<!DOCTYPE html>
<html class="has-navbar-fixed-top">
<head>
    <meta charset="utf-8">
<title>访问数据库 - wanzixin</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/outdated-browser/1.1.5/outdatedbrowser.min.css">


<link href="/zh-cn/Study/Java/Spring/%E8%AE%BF%E9%97%AE%E6%95%B0%E6%8D%AE%E5%BA%93/" rel="alternate" hreflang="zh-CN" />
    


<meta name="description" content="">





    <meta name="description" content="数据库基本上是现代应用程序的标准存储，绝大多数程序都把自己的业务数据存储在关系数据库中，可见，访问数据库几乎是所有应用程序必备能力。我们在前面已经介绍了Java程序访问数据库的标准接口JDBC，它的实现方式非常简洁，即：Java标准库定义接口，各数据库厂商以“驱动”的形式实现接口。">
<meta property="og:type" content="article">
<meta property="og:title" content="访问数据库">
<meta property="og:url" content="https://wanzixin.github.io/Study/Java/Spring/%E8%AE%BF%E9%97%AE%E6%95%B0%E6%8D%AE%E5%BA%93/index.html">
<meta property="og:site_name" content="wanzixin">
<meta property="og:description" content="数据库基本上是现代应用程序的标准存储，绝大多数程序都把自己的业务数据存储在关系数据库中，可见，访问数据库几乎是所有应用程序必备能力。我们在前面已经介绍了Java程序访问数据库的标准接口JDBC，它的实现方式非常简洁，即：Java标准库定义接口，各数据库厂商以“驱动”的形式实现接口。">
<meta property="og:locale" content="en_US">
<meta property="article:published_time" content="2021-06-28T03:19:24.000Z">
<meta property="article:modified_time" content="2021-07-03T02:00:49.550Z">
<meta property="article:author" content="wanzixin">
<meta name="twitter:card" content="summary">





<link rel="icon" href="/favicon.png">


<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Ovo|Source+Code+Pro">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bulma/0.6.2/css/bulma.min.css">


<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/lightgallery/1.6.8/css/lightgallery.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/justifiedGallery/3.6.5/css/justifiedGallery.min.css">


<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/atom-one-light.min.css">


<link rel="stylesheet" href="/css/style.css">


<script defer src="//use.fontawesome.com/releases/v5.0.8/js/all.js"></script>


    
    
    
    
    
    
    
    
    
    

    


<meta name="generator" content="Hexo 5.4.0"></head>
<body>
    
<nav class="navbar is-transparent is-fixed-top navbar-main" role="navigation" aria-label="main navigation">
    <div class="container">
        <div class="navbar-brand">
            <a class="navbar-item navbar-logo" href="/">
                
                    
                    wanzixin
                    
                
            </a>
            <div class="navbar-burger">
                <span></span>
                <span></span>
                <span></span>
            </div>
        </div>
        
        <div class="navbar-menu navbar-start">
            
            <a class="navbar-item "
               href="/archives">Archives</a>
            
            <a class="navbar-item "
               href="/categories">Categories</a>
            
            <a class="navbar-item "
               href="/categories/Diary">Diary</a>
            
            <a class="navbar-item "
               href="/categories/Gallery">Gallery</a>
            
            <a class="navbar-item "
               href="/categories/Study">Study</a>
            
            <a class="navbar-item "
               href="/categories/Item">Item</a>
            
            <a class="navbar-item "
               href="/about">About</a>
            
        </div>
        
        <div class="navbar-menu navbar-end">
            
            <a class="navbar-item search" title="Search" href="javascript:;">
                <i class="fas fa-search"></i>
            </a>
            
            
            <div class="navbar-item is-hoverable has-dropdown is-hidden-mobile is-hidden-tablet-only toc">
                <a class="navbar-item" title="Table of Contents">
                    <i class="fa fa-list"></i>
                </a>
                <div class="navbar-dropdown is-right">
                    
                    
                    
                    
                    <a class="navbar-item" href="#使用JDBC">1&nbsp;&nbsp;<b>使用JDBC</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#JdbcTemplate的用法">1.1&nbsp;&nbsp;JdbcTemplate的用法</a>
                    
                    
                    
                    <a class="navbar-item" href="#小结">1.2&nbsp;&nbsp;小结</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用声明式事务">2&nbsp;&nbsp;<b>使用声明式事务</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#回滚事务">2.1&nbsp;&nbsp;回滚事务</a>
                    
                    
                    
                    <a class="navbar-item" href="#事务边界">2.2&nbsp;&nbsp;事务边界</a>
                    
                    
                    
                    <a class="navbar-item" href="#事务传播">2.3&nbsp;&nbsp;事务传播</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用DAO">3&nbsp;&nbsp;<b>使用DAO</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#集成Hibernate">4&nbsp;&nbsp;<b>集成Hibernate</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#Insert操作">4.1&nbsp;&nbsp;Insert操作</a>
                    
                    
                    
                    <a class="navbar-item" href="#Delete操作">4.2&nbsp;&nbsp;Delete操作</a>
                    
                    
                    
                    <a class="navbar-item" href="#Update操作">4.3&nbsp;&nbsp;Update操作</a>
                    
                    
                    
                    <a class="navbar-item" href="#使用Example查询">4.4&nbsp;&nbsp;使用Example查询</a>
                    
                    
                    
                    <a class="navbar-item" href="#使用Criteria查询">4.5&nbsp;&nbsp;使用Criteria查询</a>
                    
                    
                    
                    <a class="navbar-item" href="#使用HQL查询">4.6&nbsp;&nbsp;使用HQL查询</a>
                    
                    
                    
                    <a class="navbar-item" href="#使用Hibernate原生接口">4.7&nbsp;&nbsp;使用Hibernate原生接口</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#集成JPA">5&nbsp;&nbsp;<b>集成JPA</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#集成Mybatis">6&nbsp;&nbsp;<b>集成Mybatis</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#XML配置">6.1.1&nbsp;&nbsp;XML配置</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#设计ORM">7&nbsp;&nbsp;<b>设计ORM</b></a>
                    
                </div>
            </div>
            
            
            <a class="navbar-item" title="GitHub" target="_blank" rel="noopener" href="https://github.com/wanzixin">
                
                <i class="fab fa-github"></i>
                
            </a>
               
            
        </div>
    </div>
</nav>

    <section class="section">
    <div class="container">
    <article class="article content gallery" itemscope itemprop="blogPost">
    <h1 class="article-title is-size-3 is-size-4-mobile" itemprop="name">
        
            访问数据库
        
    </h1>
    <div class="article-meta columns is-variable is-1 is-multiline is-mobile is-size-7-mobile">
        <span class="column is-narrow">
            
                <span>Jun 28 2021</span>
            
        </span>
        
        <span class="column is-narrow article-category">
            <i class="far fa-folder"></i>
            <a class="article-category-link" href="/categories/Study/">Study</a><span>></span><a class="article-category-link" href="/categories/Study/Java/">Java</a><span>></span><a class="article-category-link" href="/categories/Study/Java/Spring/">Spring</a>
        </span>
        
        
        <span class="column is-narrow">
            
            
            2 hours read (About 14044 words)
        </span>
        
    </div>
    <div class="article-entry is-size-6-mobile" itemprop="articleBody">
    
        <html><head></head><body><p>数据库基本上是现代应用程序的标准存储，绝大多数程序都把自己的业务数据存储在关系数据库中，可见，访问数据库几乎是所有应用程序必备能力。我们在前面已经介绍了Java程序访问数据库的标准接口JDBC，它的实现方式非常简洁，即：Java标准库定义接口，各数据库厂商以“驱动”的形式实现接口。<span id="more"></span>应用程序要使用哪个数据库，就把该数据库厂商的驱动以jar包形式引入进来，同时自身仅使用JDBC接口，编译期并不需要特定厂商的驱动。</p>
<p>使用JDBC虽然简单，但代码比较繁琐。Spring为了简化数据库访问，主要做了以下几点工作：</p>
<ul>
<li>提供了简化的访问JDBC的模板类，不必手动释放资源</li>
<li>提供了一个统一的DAO类以实现Data Access Object模式</li>
<li>把<code>SQLException</code>封装为<code>DataAccessException</code>，这个异常是一个<code>RuntimeException</code>，并且让我们能区分SQL异常的原因，例如，<code>DuplicateKeyException</code>表示违反了一个唯一约束</li>
<li>能方便地集成Hibernate、JPA和MyBatis这些数据库访问框架</li>
</ul>
<p>本章我们将详细讲解在Spring中访问数据库的最佳实践。</p>
<h2 id="使用JDBC"><a href="#使用JDBC" class="headerlink" title="使用JDBC"></a>使用JDBC</h2><p>Java程序使用JDBC接口访问关系数据库的时候，需要以下几步：</p>
<ul>
<li>创建全局<code>DataSource</code>实例，表示数据库连接池；</li>
<li>在需要读写数据库的方法内部，按如下步骤访问数据库：<ul>
<li>从全局<code>DataSource</code>实例获取<code>Connection</code>实例；</li>
<li>通过<code>Connection</code>实例创建<code>PreparedStatement</code>实例；</li>
<li>执行SQL语句，如果是查询，则通过<code>ResultSet</code>读取结果集，如果是修改，则获得<code>int</code>结果。</li>
</ul>
</li>
</ul>
<p>正确编写JDBC代码的关键是使用<code>try ... finally</code>释放资源，涉及到事务的代码需要正确提交或回滚事务。</p>
<p>在Spring使用JDBC，首先我们通过IoC容器创建并管理一个<code>DataSource</code>实例，然后，Spring提供了一个<code>JdbcTemplate</code>，可以方便地让我们操作JDBC，因此，通常情况下，我们会实例化一个<code>JdbcTemplate</code>。顾名思义，这个类主要使用了Template模式。</p>
<p>编写示例代码或者测试代码时，我们强烈推荐使用<a target="_blank" rel="noopener" href="http://hsqldb.org/">HSQLDB</a>这个数据库，它是一个用Java编写的关系数据库，可以以内存模式或者文件模式运行，本身只有一个jar包，非常适合演示代码或者测试代码。</p>
<p>我们以实际工程为例，先创建Maven工程<code>spring-data-jdbc</code>，然后引入以下依赖：</p>
<figure class="highlight xml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-context<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>5.2.0.RELEASE<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-jdbc<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>5.2.0.RELEASE<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>javax.annotation<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>javax.annotation-api<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.3.2<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.zaxxer<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>HikariCP<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>3.4.2<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.hsqldb<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>hsqldb<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2.5.0<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br></code></pre></td></tr></tbody></table></figure>

<p>在AppConfig中，我们需要创建以下几个必须的Bean：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Configuration</span><br><span class="hljs-meta">@ComponentScan</span><br><span class="hljs-meta">@PropertySource("jdbc.properties")</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppConfig</span> </span>{<br><br>    <span class="hljs-meta">@Value("${jdbc.url}")</span><br>    String jdbcUrl;<br><br>    <span class="hljs-meta">@Value("${jdbc.username}")</span><br>    String jdbcUsername;<br><br>    <span class="hljs-meta">@Value("${jdbc.password}")</span><br>    String jdbcPassword;<br><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-function">DataSource <span class="hljs-title">createDataSource</span><span class="hljs-params">()</span> </span>{<br>        HikariConfig config = <span class="hljs-keyword">new</span> HikariConfig();<br>        config.setJdbcUrl(jdbcUrl);<br>        config.setUsername(jdbcUsername);<br>        config.setPassword(jdbcPassword);<br>        config.addDataSourceProperty(<span class="hljs-string">"autoCommit"</span>, <span class="hljs-string">"true"</span>);<br>        config.addDataSourceProperty(<span class="hljs-string">"connectionTimeout"</span>, <span class="hljs-string">"5"</span>);<br>        config.addDataSourceProperty(<span class="hljs-string">"idleTimeout"</span>, <span class="hljs-string">"60"</span>);<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> HikariDataSource(config);<br>    }<br><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-function">JdbcTemplate <span class="hljs-title">createJdbcTemplate</span><span class="hljs-params">(<span class="hljs-meta">@Autowired</span> DataSource dataSource)</span> </span>{<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> JdbcTemplate(dataSource);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>在上述配置中：</p>
<ol>
<li>通过<code>@PropertySource("jdbc.properties")</code>读取数据库配置文件</li>
<li>通过<code>@Value("${jdbc.url}")</code>注入配置文件的相关配置</li>
<li>创建一个DataSource实例，它的实际类型是<code>HikariDataSource</code>，创建时需要用到注入的配置</li>
<li>创建一个JdbcTemplate实例，它需要注入<code>DataSource</code>，这是通过方法参数完成注入的</li>
</ol>
<p>最后，针对HSQLDB写一个配置文件<code>jdbc.properties</code>：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs plaintext"># 数据库文件名为testdb:<br>jdbc.url=jdbc:hsqldb:file:testdb<br><br># Hsqldb默认的用户名是sa，口令是空字符串:<br>jdbc.username=sa<br>jdbc.password=<br></code></pre></td></tr></tbody></table></figure>

<p>可以通过HSQLDB自带的工具来初始化数据库表，这里我们写一个Bean，在Spring容器启动时自动创建一个<code>users</code>表：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DatabaseInitializer</span> </span>{<br>    <span class="hljs-meta">@Autowired</span><br>    JdbcTemplate jdbcTemplate;<br><br>    <span class="hljs-meta">@PostConstruct</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">init</span><span class="hljs-params">()</span> </span>{<br>        jdbcTemplate.update(<span class="hljs-string">"CREATE TABLE IF NOT EXISTS users ("</span> <span class="hljs-comment">//</span><br>                + <span class="hljs-string">"id BIGINT IDENTITY NOT NULL PRIMARY KEY, "</span> <span class="hljs-comment">//</span><br>                + <span class="hljs-string">"email VARCHAR(100) NOT NULL, "</span> <span class="hljs-comment">//</span><br>                + <span class="hljs-string">"password VARCHAR(100) NOT NULL, "</span> <span class="hljs-comment">//</span><br>                + <span class="hljs-string">"name VARCHAR(100) NOT NULL, "</span> <span class="hljs-comment">//</span><br>                + <span class="hljs-string">"UNIQUE (email))"</span>);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>现在，所有准备工作都已完毕。我们只需要在需要访问数据库的Bean中，注入<code>JdbcTemplate</code>即可：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span> </span>{<br>    <span class="hljs-meta">@Autowired</span><br>    JdbcTemplate jdbcTemplate;<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<h3 id="JdbcTemplate的用法"><a href="#JdbcTemplate的用法" class="headerlink" title="JdbcTemplate的用法"></a>JdbcTemplate的用法</h3><p>Spring提供的<code>JdbcTemplate</code>采用Template模式，提供了一系列以回调为特点的工具方法，目的是避免繁琐的<code>try...catch</code>语句。</p>
<p>我们以具体的示例来说明JdbcTemplate的用法。</p>
<p>首先我们看<code>T execute(ConnectionCallback&lt;T&gt; action)</code>方法，它提供了Jdbc的<code>Connection</code>供我们使用：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">getUserById</span><span class="hljs-params">(<span class="hljs-keyword">long</span> id)</span> </span>{<br>    <span class="hljs-comment">// 注意传入的是ConnectionCallback:</span><br>    <span class="hljs-keyword">return</span> jdbcTemplate.execute((Connection conn) -&gt; {<br>        <span class="hljs-comment">// 可以直接使用conn实例，不要释放它，回调结束后JdbcTemplate自动释放:</span><br>        <span class="hljs-comment">// 在内部手动创建的PreparedStatement、ResultSet必须用try(...)释放:</span><br>        <span class="hljs-keyword">try</span> (<span class="hljs-keyword">var</span> ps = conn.prepareStatement(<span class="hljs-string">"SELECT * FROM users WHERE id = ?"</span>)) {<br>            ps.setObject(<span class="hljs-number">1</span>, id);<br>            <span class="hljs-keyword">try</span> (<span class="hljs-keyword">var</span> rs = ps.executeQuery()) {<br>                <span class="hljs-keyword">if</span> (rs.next()) {<br>                    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> User( <span class="hljs-comment">// new User object:</span><br>                            rs.getLong(<span class="hljs-string">"id"</span>), <span class="hljs-comment">// id</span><br>                            rs.getString(<span class="hljs-string">"email"</span>), <span class="hljs-comment">// email</span><br>                            rs.getString(<span class="hljs-string">"password"</span>), <span class="hljs-comment">// password</span><br>                            rs.getString(<span class="hljs-string">"name"</span>)); <span class="hljs-comment">// name</span><br>                }<br>                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(<span class="hljs-string">"user not found by id."</span>);<br>            }<br>        }<br>    });<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>也就是说，上述回调方法允许获取Connection，然后做任何基于Connection的操作。</p>
<p>我们再看<code>T execute(String sql, PreparedStatementCallback&lt;T&gt; action)</code>的用法：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">getUserByName</span><span class="hljs-params">(String name)</span> </span>{<br>    <span class="hljs-comment">// 需要传入SQL语句，以及PreparedStatementCallback:</span><br>    <span class="hljs-keyword">return</span> jdbcTemplate.execute(<span class="hljs-string">"SELECT * FROM users WHERE name = ?"</span>, (PreparedStatement ps) -&gt; {<br>        <span class="hljs-comment">// PreparedStatement实例已经由JdbcTemplate创建，并在回调后自动释放:</span><br>        ps.setObject(<span class="hljs-number">1</span>, name);<br>        <span class="hljs-keyword">try</span> (<span class="hljs-keyword">var</span> rs = ps.executeQuery()) {<br>            <span class="hljs-keyword">if</span> (rs.next()) {<br>                <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> User( <span class="hljs-comment">// new User object:</span><br>                        rs.getLong(<span class="hljs-string">"id"</span>), <span class="hljs-comment">// id</span><br>                        rs.getString(<span class="hljs-string">"email"</span>), <span class="hljs-comment">// email</span><br>                        rs.getString(<span class="hljs-string">"password"</span>), <span class="hljs-comment">// password</span><br>                        rs.getString(<span class="hljs-string">"name"</span>)); <span class="hljs-comment">// name</span><br>            }<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(<span class="hljs-string">"user not found by id."</span>);<br>        }<br>    });<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>最后，我们看<code>T queryForObject(String sql, Object[] args, RowMapper&lt;T&gt; rowMapper)</code>方法：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">getUserByEmail</span><span class="hljs-params">(String email)</span> </span>{<br>    <span class="hljs-comment">// 传入SQL，参数和RowMapper实例:</span><br>    <span class="hljs-keyword">return</span> jdbcTemplate.queryForObject(<span class="hljs-string">"SELECT * FROM users WHERE email = ?"</span>, <span class="hljs-keyword">new</span> Object[] { email },<br>            (ResultSet rs, <span class="hljs-keyword">int</span> rowNum) -&gt; {<br>                <span class="hljs-comment">// 将ResultSet的当前行映射为一个JavaBean:</span><br>                <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> User( <span class="hljs-comment">// new User object:</span><br>                        rs.getLong(<span class="hljs-string">"id"</span>), <span class="hljs-comment">// id</span><br>                        rs.getString(<span class="hljs-string">"email"</span>), <span class="hljs-comment">// email</span><br>                        rs.getString(<span class="hljs-string">"password"</span>), <span class="hljs-comment">// password</span><br>                        rs.getString(<span class="hljs-string">"name"</span>)); <span class="hljs-comment">// name</span><br>            });<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>在<code>queryForObject()</code>方法中，传入SQL以及SQL参数后，<code>JdbcTemplate</code>会自动创建<code>PreparedStatement</code>，自动执行查询并返回<code>ResultSet</code>，我们提供的<code>RowMapper</code>需要做的事情就是把<code>ResultSet</code>的当前行映射成一个JavaBean并返回。整个过程中，使用<code>Connection</code>、<code>PreparedStatement</code>和<code>ResultSet</code>都不需要我们手动管理。</p>
<p><code>RowMapper</code>不一定返回JavaBean，实际上它可以返回任何Java对象。例如，使用<code>SELECT COUNT(*)</code>查询时，可以返回<code>Long</code>：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">long</span> <span class="hljs-title">getUsers</span><span class="hljs-params">()</span> </span>{<br>    <span class="hljs-keyword">return</span> jdbcTemplate.queryForObject(<span class="hljs-string">"SELECT COUNT(*) FROM users"</span>, <span class="hljs-keyword">null</span>, (ResultSet rs, <span class="hljs-keyword">int</span> rowNum) -&gt; {<br>        <span class="hljs-comment">// SELECT COUNT(*)查询只有一列，取第一列数据:</span><br>        <span class="hljs-keyword">return</span> rs.getLong(<span class="hljs-number">1</span>);<br>    });<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>如果我们期望返回多行记录，而不是一行，可以用<code>query()</code>方法：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> List&lt;User&gt; <span class="hljs-title">getUsers</span><span class="hljs-params">(<span class="hljs-keyword">int</span> pageIndex)</span> </span>{<br>    <span class="hljs-keyword">int</span> limit = <span class="hljs-number">100</span>;<br>    <span class="hljs-keyword">int</span> offset = limit * (pageIndex - <span class="hljs-number">1</span>);<br>    <span class="hljs-keyword">return</span> jdbcTemplate.query(<span class="hljs-string">"SELECT * FROM users LIMIT ? OFFSET ?"</span>, <span class="hljs-keyword">new</span> Object[] { limit, offset },<br>            <span class="hljs-keyword">new</span> BeanPropertyRowMapper&lt;&gt;(User.class));<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>上述<code>query()</code>方法传入的参数仍然是SQL、SQL参数以及<code>RowMapper</code>实例。这里我们直接使用Spring提供的<code>BeanPropertyRowMapper</code>。如果数据库表的结构恰好和JavaBean的属性名称一致，那么<code>BeanPropertyRowMapper</code>就可以直接把一行记录按列名转换为JavaBean。</p>
<p>如果我们执行的不是查询，而是插入、更新和删除操作，那么需要使用<code>update()</code>方法：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">updateUser</span><span class="hljs-params">(User user)</span> </span>{<br>    <span class="hljs-comment">// 传入SQL，SQL参数，返回更新的行数:</span><br>    <span class="hljs-keyword">if</span> (<span class="hljs-number">1</span> != jdbcTemplate.update(<span class="hljs-string">"UPDATE user SET name = ? WHERE id=?"</span>, user.getName(), user.getId())) {<br>        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(<span class="hljs-string">"User not found by id"</span>);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>只有一种<code>INSERT</code>操作比较特殊，那就是如果某一列是自增列（例如自增主键），通常，我们需要获取插入后的自增值。<code>JdbcTemplate</code>提供了一个<code>KeyHolder</code>来简化这一操作：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">register</span><span class="hljs-params">(String email, String password, String name)</span> </span>{<br>    <span class="hljs-comment">// 创建一个KeyHolder:</span><br>    KeyHolder holder = <span class="hljs-keyword">new</span> GeneratedKeyHolder();<br>    <span class="hljs-keyword">if</span> (<span class="hljs-number">1</span> != jdbcTemplate.update(<br>        <span class="hljs-comment">// 参数1:PreparedStatementCreator</span><br>        (conn) -&gt; {<br>            <span class="hljs-comment">// 创建PreparedStatement时，必须指定RETURN_GENERATED_KEYS:</span><br>            <span class="hljs-keyword">var</span> ps = conn.prepareStatement(<span class="hljs-string">"INSERT INTO users(email,password,name) VALUES(?,?,?)"</span>,<br>                    Statement.RETURN_GENERATED_KEYS);<br>            ps.setObject(<span class="hljs-number">1</span>, email);<br>            ps.setObject(<span class="hljs-number">2</span>, password);<br>            ps.setObject(<span class="hljs-number">3</span>, name);<br>            <span class="hljs-keyword">return</span> ps;<br>        },<br>        <span class="hljs-comment">// 参数2:KeyHolder</span><br>        holder)<br>    ) {<br>        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(<span class="hljs-string">"Insert failed."</span>);<br>    }<br>    <span class="hljs-comment">// 从KeyHolder中获取返回的自增值:</span><br>    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> User(holder.getKey().longValue(), email, password, name);<br>}<br></code></pre></td></tr></tbody></table></figure>

<p><code>JdbcTemplate</code>还有许多重载方法，这里我们不一一介绍。需要强调的是，<code>JdbcTemplate</code>只是对JDBC操作的一个简单封装，它的目的是尽量减少手动编写<code>try(resource) {...}</code>的代码。对于查询，主要通过<code>RowMapper</code>实现了JDBC结果集到Java对象的转换。</p>
<p>我们总结一下<code>JdbcTemplate</code>的用法，那就是：</p>
<ul>
<li>针对简单查询，优选<code>query()</code>和<code>queryForObject()</code>，因为只需提供SQL语句、参数和<code>RowMapper</code>；</li>
<li>针对更新操作，优选<code>update()</code>，因为只需提供SQL语句和参数；</li>
<li>任何复杂的操作，最终也可以通过<code>execute(ConnectionCallback)</code>实现，因为拿到<code>Connection</code>就可以做任何JDBC操作。</li>
</ul>
<p>实际上我们使用最多的仍然是各种查询。如果在设计表结构的时候，能够和JavaBean的属性一一对应，那么直接使用<code>BeanPropertyRowMapper</code>就很方便。如果表结构和JavaBean不一致怎么办？那就需要稍微改写一下查询，使结果集的结构和JavaBean保持一致。</p>
<p>例如，表的列名是<code>office_address</code>，而JavaBean属性是<code>workAddress</code>，就需要指定别名，改写查询如下：</p>
<figure class="highlight sql hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">SELECT</span> id, email, office_address <span class="hljs-keyword">AS</span> workAddress, name <span class="hljs-keyword">FROM</span> users <span class="hljs-keyword">WHERE</span> email <span class="hljs-operator">=</span> ?<br></code></pre></td></tr></tbody></table></figure>

<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>Spring提供了<code>JdbcTemplate</code>来简化JDBC操作；</p>
<p>使用<code>JdbcTemplate</code>时，根据需要优先选择高级方法；</p>
<p>任何JDBC操作都可以使用保底的<code>execute(ConnectionCallback)</code>方法。</p>
<h2 id="使用声明式事务"><a href="#使用声明式事务" class="headerlink" title="使用声明式事务"></a>使用声明式事务</h2><p>使用Spring操作JDBC虽然方便，但是我们在前面讨论JDBC的时候，讲到过JDBC事务，如果要在Spring中操作事务，没必要手写JDBC事务，可以使用Spring提供的高级接口来操作事务。</p>
<p>Spring提供了一个<code>PlatformTransactionManager</code>来表示事务管理器，所有的事务都由它负责管理。而事务由<code>TransactionStatus</code>表示。如果手写事务代码，使用<code>try...catch</code>如下：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs java">TransactionStatus tx = <span class="hljs-keyword">null</span>;<br><span class="hljs-keyword">try</span> {<br>    <span class="hljs-comment">// 开启事务:</span><br>    tx = txManager.getTransaction(<span class="hljs-keyword">new</span> DefaultTransactionDefinition());<br>    <span class="hljs-comment">// 相关JDBC操作:</span><br>    jdbcTemplate.update(<span class="hljs-string">"..."</span>);<br>    jdbcTemplate.update(<span class="hljs-string">"..."</span>);<br>    <span class="hljs-comment">// 提交事务:</span><br>    txManager.commit(tx);<br>} <span class="hljs-keyword">catch</span> (RuntimeException e) {<br>    <span class="hljs-comment">// 回滚事务:</span><br>    txManager.rollback(tx);<br>    <span class="hljs-keyword">throw</span> e;<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>Spring为啥要抽象出<code>PlatformTransactionManager</code>和<code>TransactionStatus</code>？原因是JavaEE除了提供JDBC事务外，它还支持分布式事务JTA（Java Transaction API）。分布式事务是指多个数据源（比如多个数据库，多个消息系统）要在分布式环境下实现事务的时候，应该怎么实现。分布式事务实现起来非常复杂，简单地说就是通过一个分布式事务管理器实现两阶段提交，但本身数据库事务就不快，基于数据库事务实现的分布式事务就慢得难以忍受，所以使用率不高。</p>
<p>Spring为了同时支持JDBC和JTA两种事务模型，就抽象出<code>PlatformTransactionManager</code>。因为我们的代码只需要JDBC事务，因此，在<code>AppConfig</code>中，需要再定义一个<code>PlatformTransactionManager</code>对应的Bean，它的实际类型是<code>DataSourceTransactionManager</code>：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Configuration</span><br><span class="hljs-meta">@ComponentScan</span><br><span class="hljs-meta">@PropertySource("jdbc.properties")</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppConfig</span> </span>{<br>    ...<br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-function">PlatformTransactionManager <span class="hljs-title">createTxManager</span><span class="hljs-params">(<span class="hljs-meta">@Autowired</span> DataSource dataSource)</span> </span>{<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> DataSourceTransactionManager(dataSource);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>使用编程的方式使用Spring事务仍然比较繁琐，更好的方式是通过声明式事务来实现。使用声明式事务非常简单，除了在<code>AppConfig</code>中追加一个上述定义的<code>PlatformTransactionManager</code>外，再加一个<code>@EnableTransactionManagement</code>就可以启用声明式事务：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Configuration</span><br><span class="hljs-meta">@ComponentScan</span><br><span class="hljs-meta">@EnableTransactionManagement</span> <span class="hljs-comment">// 启用声明式</span><br><span class="hljs-meta">@PropertySource("jdbc.properties")</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppConfig</span> </span>{<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>然后，对需要事务支持的方法，加一个<code>@Transactional</code>注解：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span> </span>{<br>    <span class="hljs-comment">// 此public方法自动具有事务支持:</span><br>    <span class="hljs-meta">@Transactional</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">register</span><span class="hljs-params">(String email, String password, String name)</span> </span>{<br>       ...<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>或者更简单一点，直接在Bean的<code>class</code>处加上，表示所有<code>public</code>方法都具有事务支持：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-meta">@Transactional</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span> </span>{<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>Spring对一个声明式事务的方法，如何开启事务支持？原理仍然是AOP代理，即通过自动创建Bean的Proxy实现：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span>$$<span class="hljs-title">EnhancerBySpringCGLIB</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">UserService</span> </span>{<br>    UserService target = ...<br>    PlatformTransactionManager txManager = ...<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">register</span><span class="hljs-params">(String email, String password, String name)</span> </span>{<br>        TransactionStatus tx = <span class="hljs-keyword">null</span>;<br>        <span class="hljs-keyword">try</span> {<br>            tx = txManager.getTransaction(<span class="hljs-keyword">new</span> DefaultTransactionDefinition());<br>            target.register(email, password, name);<br>            txManager.commit(tx);<br>        } <span class="hljs-keyword">catch</span> (RuntimeException e) {<br>            txManager.rollback(tx);<br>            <span class="hljs-keyword">throw</span> e;<br>        }<br>    }<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>注意：声明了<code>@EnableTransactionManagement</code>后，不必额外添加<code>@EnableAspectJAutoProxy</code>。</p>
<h3 id="回滚事务"><a href="#回滚事务" class="headerlink" title="回滚事务"></a>回滚事务</h3><p>默认情况下，如果发生了<code>RuntimeException</code>，Spring的声明式事务将自动回滚。在一个事务方法中，如果程序判断需要回滚事务，只需抛出<code>RuntimeException</code>，例如：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Transactional</span><br><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">buyProducts</span><span class="hljs-params">(<span class="hljs-keyword">long</span> productId, <span class="hljs-keyword">int</span> num)</span> </span>{<br>    ...<br>    <span class="hljs-keyword">if</span> (store &lt; num) {<br>        <span class="hljs-comment">// 库存不够，购买失败:</span><br>        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalArgumentException(<span class="hljs-string">"No enough products"</span>);<br>    }<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>如果要针对Checked Exception回滚事务，需要在<code>@Transactional</code>注解中写出来：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Transactional(rollbackFor = {RuntimeException.class, IOException.class})</span><br><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">buyProducts</span><span class="hljs-params">(<span class="hljs-keyword">long</span> productId, <span class="hljs-keyword">int</span> num)</span> <span class="hljs-keyword">throws</span> IOException </span>{<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>上述代码表示在抛出<code>RuntimeException</code>或<code>IOException</code>时，事务将回滚。</p>
<p>为了简化代码，我们强烈建议业务异常体系从<code>RuntimeException</code>派生，这样就不必声明任何特殊异常即可让Spring的声明式事务正常工作：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BusinessException</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">RuntimeException</span> </span>{<br>    ...<br>}<br><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LoginException</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">BusinessException</span> </span>{<br>    ...<br>}<br><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PaymentException</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">BusinessException</span> </span>{<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<h3 id="事务边界"><a href="#事务边界" class="headerlink" title="事务边界"></a>事务边界</h3><p>在使用事务的时候，明确事务边界非常重要。对于声明式事务，例如，下面的<code>register()</code>方法：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span> </span>{<br>    <span class="hljs-meta">@Transactional</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">register</span><span class="hljs-params">(String email, String password, String name)</span> </span>{ <span class="hljs-comment">// 事务开始</span><br>       ...<br>    } <span class="hljs-comment">// 事务结束</span><br>}<br></code></pre></td></tr></tbody></table></figure>

<p>它的事务边界就是<code>register()</code>方法开始和结束。</p>
<p>类似的，一个负责给用户增加积分的<code>addBonus()</code>方法：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BonusService</span> </span>{<br>    <span class="hljs-meta">@Transactional</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">addBonus</span><span class="hljs-params">(<span class="hljs-keyword">long</span> userId, <span class="hljs-keyword">int</span> bonus)</span> </span>{ <span class="hljs-comment">// 事务开始</span><br>       ...<br>    } <span class="hljs-comment">// 事务结束</span><br>}<br></code></pre></td></tr></tbody></table></figure>

<p>它的事务边界就是<code>addBonus()</code>方法开始和结束。</p>
<p>在现实世界中，问题总是要复杂一点点。用户注册后，能自动获得100积分，因此，实际代码如下：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span> </span>{<br>    <span class="hljs-meta">@Autowired</span><br>    BonusService bonusService;<br><br>    <span class="hljs-meta">@Transactional</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">register</span><span class="hljs-params">(String email, String password, String name)</span> </span>{<br>        <span class="hljs-comment">// 插入用户记录:</span><br>        User user = jdbcTemplate.insert(<span class="hljs-string">"..."</span>);<br>        <span class="hljs-comment">// 增加100积分:</span><br>        bonusService.addBonus(user.id, <span class="hljs-number">100</span>);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>现在问题来了：调用方（比如<code>RegisterController</code>）调用<code>UserService.register()</code>这个事务方法，它在内部又调用了<code>BonusService.addBonus()</code>这个事务方法，一共有几个事务？如果<code>addBonus()</code>抛出了异常需要回滚事务，<code>register()</code>方法的事务是否也要回滚？</p>
<p>问题的复杂度是不是一下子提高了10倍？</p>
<h3 id="事务传播"><a href="#事务传播" class="headerlink" title="事务传播"></a>事务传播</h3><p>要解决上面的问题，我们首先要定义事务的传播模型。</p>
<p>假设用户注册的入口是<code>RegisterController</code>，它本身没有事务，仅仅是调用<code>UserService.register()</code>这个事务方法：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Controller</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RegisterController</span> </span>{<br>    <span class="hljs-meta">@Autowired</span><br>    UserService userService;<br><br>    <span class="hljs-meta">@PostMapping("/register")</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> ModelAndView <span class="hljs-title">doRegister</span><span class="hljs-params">(HttpServletRequest req)</span> </span>{<br>        String email = req.getParameter(<span class="hljs-string">"email"</span>);<br>        String password = req.getParameter(<span class="hljs-string">"password"</span>);<br>        String name = req.getParameter(<span class="hljs-string">"name"</span>);<br>        User user = userService.register(email, password, name);<br>        <span class="hljs-keyword">return</span> ...<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>因此，<code>UserService.register()</code>这个事务方法的起始和结束，就是事务的范围。</p>
<p>我们需要关心的问题是，在<code>UserService.register()</code>这个事务方法内，调用<code>BonusService.addBonus()</code>，我们期待的事务行为是什么：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Transactional</span><br><span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">register</span><span class="hljs-params">(String email, String password, String name)</span> </span>{<br>    <span class="hljs-comment">// 事务已开启:</span><br>    User user = jdbcTemplate.insert(<span class="hljs-string">"..."</span>);<br>    <span class="hljs-comment">// ???:</span><br>    bonusService.addBonus(user.id, <span class="hljs-number">100</span>);<br>} <span class="hljs-comment">// 事务结束</span><br></code></pre></td></tr></tbody></table></figure>

<p>对于大多数业务来说，我们期待<code>BonusService.addBonus()</code>的调用，和<code>UserService.register()</code>应当融合在一起，它的行为应该如下：</p>
<p><code>UserService.register()</code>已经开启了一个事务，那么在内部调用<code>BonusService.addBonus()</code>时，<code>BonusService.addBonus()</code>方法就没必要再开启一个新事务，直接加入到<code>BonusService.register()</code>的事务里就好了。</p>
<p>其实就相当于：</p>
<ol>
<li><code>UserService.register()</code>先执行了一条INSERT语句：<code>INSERT INTO users ...</code></li>
<li><code>BonusService.addBonus()</code>再执行一条INSERT语句：<code>INSERT INTO bonus ...</code></li>
</ol>
<p>因此，Spring的声明式事务为事务传播定义了几个级别，默认传播级别就是REQUIRED，它的意思是，如果当前没有事务，就创建一个新事务，如果当前有事务，就加入到当前事务中执行。</p>
<p>我们观察<code>UserService.register()</code>方法，它在<code>RegisterController</code>中执行，因为<code>RegisterController</code>没有事务，因此，<code>UserService.register()</code>方法会自动创建一个新事务。</p>
<p>在<code>UserService.register()</code>方法内部，调用<code>BonusService.addBonus()</code>方法时，因为<code>BonusService.addBonus()</code>检测到当前已经有事务了，因此，它会加入到当前事务中执行。</p>
<p>因此，整个业务流程的事务边界就清晰了：它只有一个事务，并且范围就是<code>UserService.register()</code>方法。</p>
<p>有的童鞋会问：把<code>BonusService.addBonus()</code>方法的<code>@Transactional</code>去掉，变成一个普通方法，那不就规避了复杂的传播模型吗？</p>
<p>去掉<code>BonusService.addBonus()</code>方法的<code>@Transactional</code>，会引来另一个问题，即其他地方如果调用<code>BonusService.addBonus()</code>方法，那就没法保证事务了。例如，规定用户登录时积分+5：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Controller</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LoginController</span> </span>{<br>    <span class="hljs-meta">@Autowired</span><br>    BonusService bonusService;<br><br>    <span class="hljs-meta">@PostMapping("/login")</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> ModelAndView <span class="hljs-title">doLogin</span><span class="hljs-params">(HttpServletRequest req)</span> </span>{<br>        User user = ...<br>        bonusService.addBonus(user.id, <span class="hljs-number">5</span>);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>可见，<code>BonusService.addBonus()</code>方法必须要有<code>@Transactional</code>，否则，登录后积分就无法添加了。</p>
<p>默认的事务传播级别是<code>REQUIRED</code>，它满足绝大部分的需求。还有一些其他的传播级别：</p>
<p><code>SUPPORTS</code>：表示如果有事务，就加入到当前事务，如果没有，那也不开启事务执行。这种传播级别可用于查询方法，因为SELECT语句既可以在事务内执行，也可以不需要事务；</p>
<p><code>MANDATORY</code>：表示必须要存在当前事务并加入执行，否则将抛出异常。这种传播级别可用于核心更新逻辑，比如用户余额变更，它总是被其他事务方法调用，不能直接由非事务方法调用；</p>
<p><code>REQUIRES_NEW</code>：表示不管当前有没有事务，都必须开启一个新的事务执行。如果当前已经有事务，那么当前事务会挂起，等新事务完成后，再恢复执行；</p>
<p><code>NOT_SUPPORTED</code>：表示不支持事务，如果当前有事务，那么当前事务会挂起，等这个方法执行完成后，再恢复执行；</p>
<p><code>NEVER</code>：和<code>NOT_SUPPORTED</code>相比，它不但不支持事务，而且在监测到当前有事务时，会抛出异常拒绝执行；</p>
<p><code>NESTED</code>：表示如果当前有事务，则开启一个嵌套级别事务，如果当前没有事务，则开启一个新事务。</p>
<p>上面这么多种事务的传播级别，其实默认的<code>REQUIRED</code>已经满足绝大部分需求，<code>SUPPORTS</code>和<code>REQUIRES_NEW</code>在少数情况下会用到，其他基本不会用到，因为把事务搞得越复杂，不仅逻辑跟着复杂，而且速度也会越慢。</p>
<p>定义事务的传播级别也是写在<code>@Transactional</code>注解里的：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Transactional(propagation = Propagation.REQUIRES_NEW)</span><br><span class="hljs-function"><span class="hljs-keyword">public</span> Product <span class="hljs-title">createProduct</span><span class="hljs-params">()</span> </span>{<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>现在只剩最后一个问题了：Spring是如何传播事务的？</p>
<p>我们<a target="_blank" rel="noopener" href="https://www.liaoxuefeng.com/wiki/1252599548343744/1321748500840481">在JDBC中使用事务</a>的时候，是这么个写法：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java">Connection conn = openConnection();<br><span class="hljs-keyword">try</span> {<br>    <span class="hljs-comment">// 关闭自动提交:</span><br>    conn.setAutoCommit(<span class="hljs-keyword">false</span>);<br>    <span class="hljs-comment">// 执行多条SQL语句:</span><br>    insert(); update(); delete();<br>    <span class="hljs-comment">// 提交事务:</span><br>    conn.commit();<br>} <span class="hljs-keyword">catch</span> (SQLException e) {<br>    <span class="hljs-comment">// 回滚事务:</span><br>    conn.rollback();<br>} <span class="hljs-keyword">finally</span> {<br>    conn.setAutoCommit(<span class="hljs-keyword">true</span>);<br>    conn.close();<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>Spring使用声明式事务，最终也是通过执行JDBC事务来实现功能的，那么，一个事务方法，如何获知当前是否存在事务？</p>
<p>答案是<a target="_blank" rel="noopener" href="https://www.liaoxuefeng.com/wiki/1252599548343744/1306581251653666">使用ThreadLocal</a>。Spring总是把JDBC相关的<code>Connection</code>和<code>TransactionStatus</code>实例绑定到<code>ThreadLocal</code>。如果一个事务方法从<code>ThreadLocal</code>未取到事务，那么它会打开一个新的JDBC连接，同时开启一个新的事务，否则，它就直接使用从<code>ThreadLocal</code>获取的JDBC连接以及<code>TransactionStatus</code>。</p>
<p>因此，事务能正确传播的前提是，方法调用是在一个线程内才行。如果像下面这样写：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Transactional</span><br><span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">register</span><span class="hljs-params">(String email, String password, String name)</span> </span>{ <span class="hljs-comment">// BEGIN TX-A</span><br>    User user = jdbcTemplate.insert(<span class="hljs-string">"..."</span>);<br>    <span class="hljs-keyword">new</span> Thread(() -&gt; {<br>        <span class="hljs-comment">// BEGIN TX-B:</span><br>        bonusService.addBonus(user.id, <span class="hljs-number">100</span>);<br>        <span class="hljs-comment">// END TX-B</span><br>    }).start();<br>} <span class="hljs-comment">// END TX-A</span><br></code></pre></td></tr></tbody></table></figure>

<p>在另一个线程中调用<code>BonusService.addBonus()</code>，它根本获取不到当前事务，因此，<code>UserService.register()</code>和<code>BonusService.addBonus()</code>两个方法，将分别开启两个完全独立的事务。</p>
<p>换句话说，事务只能在当前线程传播，无法跨线程传播。</p>
<p>那如果我们想实现跨线程传播事务呢？原理很简单，就是要想办法把当前线程绑定到<code>ThreadLocal</code>的<code>Connection</code>和<code>TransactionStatus</code>实例传递给新线程，但实现起来非常复杂，根据异常回滚更加复杂，不推荐自己去实现。</p>
<p><strong>Spring提供的声明式事物极大地方便了在数据库中使用事务，正确使用声明式事务的关键在于确定好事务边界，理解事物传播级别。</strong></p>
<h2 id="使用DAO"><a href="#使用DAO" class="headerlink" title="使用DAO"></a>使用DAO</h2><p>在传统的多层应用程序中，通常是Web层调用业务层，业务层调用数据访问层。业务层负责处理各种业务逻辑，而数据访问层只负责对数据进行增删改查。因此，实现数据访问层就是用<code>JdbcTemplate</code>实现对数据库的操作。</p>
<p>编写数据访问层的时候，可以使用DAO模式。DAO即Data Access Object的缩写，它没有什么神秘之处，实现起来基本如下：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserDao</span> </span>{<br><br>    <span class="hljs-meta">@Autowired</span><br>    JdbcTemplate jdbcTemplate;<br><br>    <span class="hljs-function">User <span class="hljs-title">getById</span><span class="hljs-params">(<span class="hljs-keyword">long</span> id)</span> </span>{<br>        ...<br>    }<br><br>    <span class="hljs-function">List&lt;User&gt; <span class="hljs-title">getUsers</span><span class="hljs-params">(<span class="hljs-keyword">int</span> page)</span> </span>{<br>        ...<br>    }<br><br>    <span class="hljs-function">User <span class="hljs-title">createUser</span><span class="hljs-params">(User user)</span> </span>{<br>        ...<br>    }<br><br>    <span class="hljs-function">User <span class="hljs-title">updateUser</span><span class="hljs-params">(User user)</span> </span>{<br>        ...<br>    }<br><br>    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">deleteUser</span><span class="hljs-params">(User user)</span> </span>{<br>        ...<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>Spring提供了一个<code>JdbcDaoSupport</code>类，用于简化DAO的实现。这个<code>JdbcDaoSupport</code>没什么复杂的，核心代码就是持有一个<code>JdbcTemplate</code>：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">JdbcDaoSupport</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">DaoSupport</span> </span>{<br><br>    <span class="hljs-keyword">private</span> JdbcTemplate jdbcTemplate;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setJdbcTemplate</span><span class="hljs-params">(JdbcTemplate jdbcTemplate)</span> </span>{<br>        <span class="hljs-keyword">this</span>.jdbcTemplate = jdbcTemplate;<br>        initTemplateConfig();<br>    }<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> JdbcTemplate <span class="hljs-title">getJdbcTemplate</span><span class="hljs-params">()</span> </span>{<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.jdbcTemplate;<br>    }<br><br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>它的意图是子类直接从<code>JdbcDaoSupport</code>继承后，可以随时调用<code>getJdbcTemplate()</code>获得<code>JdbcTemplate</code>的实例。那么问题来了：因为<code>JdbcDaoSupport</code>的<code>jdbcTemplate</code>字段没有标记<code>@Autowired</code>，所以，子类想要注入<code>JdbcTemplate</code>，还得自己想个办法：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-meta">@Transactional</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserDao</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">JdbcDaoSupport</span> </span>{<br>    <span class="hljs-meta">@Autowired</span><br>    JdbcTemplate jdbcTemplate;<br><br>    <span class="hljs-meta">@PostConstruct</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">init</span><span class="hljs-params">()</span> </span>{<br>        <span class="hljs-keyword">super</span>.setJdbcTemplate(jdbcTemplate);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>有的童鞋可能看出来了：既然<code>UserDao</code>都已经注入了<code>JdbcTemplate</code>，那再把它放到父类里，通过<code>getJdbcTemplate()</code>访问岂不是多此一举？</p>
<p>如果使用传统的XML配置，并不需要编写<code>@Autowired JdbcTemplate jdbcTemplate</code>，但是考虑到现在基本上是使用注解的方式，我们可以编写一个<code>AbstractDao</code>，专门负责注入<code>JdbcTemplate</code>：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AbstractDao</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">JdbcDaoSupport</span> </span>{<br>    <span class="hljs-meta">@Autowired</span><br>    <span class="hljs-keyword">private</span> JdbcTemplate jdbcTemplate;<br><br>    <span class="hljs-meta">@PostConstruct</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">init</span><span class="hljs-params">()</span> </span>{<br>        <span class="hljs-keyword">super</span>.setJdbcTemplate(jdbcTemplate);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>这样，子类的代码就非常干净，可以直接调用<code>getJdbcTemplate()</code>：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-meta">@Transactional</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserDao</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractDao</span> </span>{<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">getById</span><span class="hljs-params">(<span class="hljs-keyword">long</span> id)</span> </span>{<br>        <span class="hljs-keyword">return</span> getJdbcTemplate().queryForObject(<br>                <span class="hljs-string">"SELECT * FROM users WHERE id = ?"</span>,<br>                <span class="hljs-keyword">new</span> BeanPropertyRowMapper&lt;&gt;(User.class),<br>                id<br>        );<br>    }<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>倘若肯再多写一点样板代码，就可以把<code>AbstractDao</code>改成泛型，并实现<code>getById()</code>，<code>getAll()</code>，<code>deleteById()</code>这样的通用方法：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AbstractDao</span>&lt;<span class="hljs-title">T</span>&gt; <span class="hljs-keyword">extends</span> <span class="hljs-title">JdbcDaoSupport</span> </span>{<br>    <span class="hljs-keyword">private</span> String table;<br>    <span class="hljs-keyword">private</span> Class&lt;T&gt; entityClass;<br>    <span class="hljs-keyword">private</span> RowMapper&lt;T&gt; rowMapper;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">AbstractDao</span><span class="hljs-params">()</span> </span>{<br>        <span class="hljs-comment">// 获取当前类型的泛型类型:</span><br>        <span class="hljs-keyword">this</span>.entityClass = getParameterizedType();<br>        <span class="hljs-keyword">this</span>.table = <span class="hljs-keyword">this</span>.entityClass.getSimpleName().toLowerCase() + <span class="hljs-string">"s"</span>;<br>        <span class="hljs-keyword">this</span>.rowMapper = <span class="hljs-keyword">new</span> BeanPropertyRowMapper&lt;&gt;(entityClass);<br>    }<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> T <span class="hljs-title">getById</span><span class="hljs-params">(<span class="hljs-keyword">long</span> id)</span> </span>{<br>        <span class="hljs-keyword">return</span> getJdbcTemplate().queryForObject(<span class="hljs-string">"SELECT * FROM "</span> + table + <span class="hljs-string">" WHERE id = ?"</span>, <span class="hljs-keyword">this</span>.rowMapper, id);<br>    }<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> List&lt;T&gt; <span class="hljs-title">getAll</span><span class="hljs-params">(<span class="hljs-keyword">int</span> pageIndex)</span> </span>{<br>        <span class="hljs-keyword">int</span> limit = <span class="hljs-number">100</span>;<br>        <span class="hljs-keyword">int</span> offset = limit * (pageIndex - <span class="hljs-number">1</span>);<br>        <span class="hljs-keyword">return</span> getJdbcTemplate().query(<span class="hljs-string">"SELECT * FROM "</span> + table + <span class="hljs-string">" LIMIT ? OFFSET ?"</span>,<br>                <span class="hljs-keyword">new</span> Object[] { limit, offset },<br>                <span class="hljs-keyword">this</span>.rowMapper);<br>    }<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">deleteById</span><span class="hljs-params">(<span class="hljs-keyword">long</span> id)</span> </span>{<br>        getJdbcTemplate().update(<span class="hljs-string">"DELETE FROM "</span> + table + <span class="hljs-string">" WHERE id = ?"</span>, id);<br>    }<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>这样，每个子类就自动获得了这些通用方法：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-meta">@Transactional</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserDao</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractDao</span>&lt;<span class="hljs-title">User</span>&gt; </span>{<br>    <span class="hljs-comment">// 已经有了:</span><br>    <span class="hljs-comment">// User getById(long)</span><br>    <span class="hljs-comment">// List&lt;User&gt; getAll(int)</span><br>    <span class="hljs-comment">// void deleteById(long)</span><br>}<br><br><span class="hljs-meta">@Component</span><br><span class="hljs-meta">@Transactional</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BookDao</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractDao</span>&lt;<span class="hljs-title">Book</span>&gt; </span>{<br>    <span class="hljs-comment">// 已经有了:</span><br>    <span class="hljs-comment">// Book getById(long)</span><br>    <span class="hljs-comment">// List&lt;Book&gt; getAll(int)</span><br>    <span class="hljs-comment">// void deleteById(long)</span><br>}<br></code></pre></td></tr></tbody></table></figure>

<p>可见，DAO模式就是一个简单的数据访问模式，是否使用DAO，根据实际情况决定，因为很多时候，直接在Service层操作数据库也是完全没有问题的。</p>
<h2 id="集成Hibernate"><a href="#集成Hibernate" class="headerlink" title="集成Hibernate"></a>集成Hibernate</h2><p>使用<code>JdbcTemplate</code>的时候，我们用的最多的方法就是<code>List&lt;T&gt; query(String sql, Object[] args, RowMapper rowMapper)</code>，这个<code>RowMapper</code>的作用是把ResultSet的一行记录映射为Java Bean。这种把关系数据库的表记录转换为Java对象的过程就是<strong>ORM：Object Relational Mapping</strong>。ORM既可以把行记录转换成行对象，也可以把Java对象转换为行记录。</p>
<p>使用JdbcTemplate配合RowMapper可以看作最原始的ORM，如果要实现更自动化的ORM，可以选择更成熟的ORM框架，例如Hibernate。</p>
<p>我们来看看如何在Spring中集成Hibernate。</p>
<p>Hibernate作为ORM框架，它可以替代<code>JdbcTemplate</code>，但Hibernate仍然需要JDBC驱动，所以，我们需要引入JDBC驱动、连接池，以及Hibernate本身。在Maven中，我们加入以下依赖项：</p>
<figure class="highlight xml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-comment">&lt;!-- JDBC驱动，这里使用HSQLDB --&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.hsqldb<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>hsqldb<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2.5.0<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br><br><span class="hljs-comment">&lt;!-- JDBC连接池 --&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.zaxxer<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>HikariCP<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>3.4.2<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br><br><span class="hljs-comment">&lt;!-- Hibernate --&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.hibernate<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>hibernate-core<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>5.4.2.Final<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br><br><span class="hljs-comment">&lt;!-- Spring Context和Spring ORM --&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-context<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>5.2.0.RELEASE<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-orm<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>5.2.0.RELEASE<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br></code></pre></td></tr></tbody></table></figure>

<p>在AppConfig中，我们仍然需要创建DataSource、引入JDBC配置文件，以及启用声明式事务：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Configuration</span><br><span class="hljs-meta">@ComponentScan</span><br><span class="hljs-meta">@EnableTransactionManagement</span><br><span class="hljs-meta">@PropertySource("jdbc.properties")</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppConfig</span> </span>{<br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-function">DataSource <span class="hljs-title">createDataSource</span><span class="hljs-params">()</span> </span>{<br>        ...<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>为了启用Hibernate，我们需要创建一个<code>LocalSessionFactoryBean</code>：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppConfig</span> </span>{<br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-function">LocalSessionFactoryBean <span class="hljs-title">createSessionFactory</span><span class="hljs-params">(<span class="hljs-meta">@Autowired</span> DataSource dataSource)</span> </span>{<br>        <span class="hljs-keyword">var</span> props = <span class="hljs-keyword">new</span> Properties();<br>        props.setProperty(<span class="hljs-string">"hibernate.hbm2ddl.auto"</span>, <span class="hljs-string">"update"</span>); <span class="hljs-comment">// 生产环境不要使用</span><br>        props.setProperty(<span class="hljs-string">"hibernate.dialect"</span>, <span class="hljs-string">"org.hibernate.dialect.HSQLDialect"</span>);<br>        props.setProperty(<span class="hljs-string">"hibernate.show_sql"</span>, <span class="hljs-string">"true"</span>);<br>        <span class="hljs-keyword">var</span> sessionFactoryBean = <span class="hljs-keyword">new</span> LocalSessionFactoryBean();<br>        sessionFactoryBean.setDataSource(dataSource);<br>        <span class="hljs-comment">// 扫描指定的package获取所有entity class:</span><br>        sessionFactoryBean.setPackagesToScan(<span class="hljs-string">"com.itranswarp.learnjava.entity"</span>);<br>        sessionFactoryBean.setHibernateProperties(props);<br>        <span class="hljs-keyword">return</span> sessionFactoryBean;<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>注意我们在<a target="_blank" rel="noopener" href="https://www.liaoxuefeng.com/wiki/1252599548343744/1308043627200545">定制Bean</a>中讲到过<code>FactoryBean</code>，<code>LocalSessionFactoryBean</code>是一个<code>FactoryBean</code>，它会再自动创建一个<code>SessionFactory</code>，在Hibernate中，<code>Session</code>是封装了一个JDBC <code>Connection</code>的实例，而<code>SessionFactory</code>是封装了JDBC <code>DataSource</code>的实例，即<code>SessionFactory</code>持有连接池，每次需要操作数据库的时候，<code>SessionFactory</code>创建一个新的<code>Session</code>，相当于从连接池获取到一个新的<code>Connection</code>。<code>SessionFactory</code>就是Hibernate提供的最核心的一个对象，但<code>LocalSessionFactoryBean</code>是Spring提供的为了让我们方便创建<code>SessionFactory</code>的类。</p>
<p>注意到上面创建<code>LocalSessionFactoryBean</code>的代码，首先用<code>Properties</code>持有Hibernate初始化<code>SessionFactory</code>时用到的所有设置，常用的设置请参考<a target="_blank" rel="noopener" href="https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#configurations">Hibernate文档</a>，这里我们只定义了3个设置：</p>
<ul>
<li><code>hibernate.hbm2ddl.auto=update</code>：表示自动创建数据库的表结构，注意不要在生产环境中启用；</li>
<li><code>hibernate.dialect=org.hibernate.dialect.HSQLDialect</code>：指示Hibernate使用的数据库是HSQLDB。Hibernate使用一种HQL的查询语句，它和SQL类似，但真正在“翻译”成SQL时，会根据设定的数据库“方言”来生成针对数据库优化的SQL；</li>
<li><code>hibernate.show_sql=true</code>：让Hibernate打印执行的SQL，这对于调试非常有用，我们可以方便地看到Hibernate生成的SQL语句是否符合我们的预期。</li>
</ul>
<p>除了设置<code>DataSource</code>和<code>Properties</code>之外，注意到<code>setPackagesToScan()</code>我们传入了一个<code>package</code>名称，它指示Hibernate扫描这个包下面的所有Java类，自动找出能映射为数据库表记录的JavaBean。后面我们会仔细讨论如何编写符合Hibernate要求的JavaBean。</p>
<p>紧接着，我们还需要创建<code>HibernateTemplate</code>以及<code>HibernateTransactionManager</code>：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppConfig</span> </span>{<br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-function">HibernateTemplate <span class="hljs-title">createHibernateTemplate</span><span class="hljs-params">(<span class="hljs-meta">@Autowired</span> SessionFactory sessionFactory)</span> </span>{<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> HibernateTemplate(sessionFactory);<br>    }<br><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-function">PlatformTransactionManager <span class="hljs-title">createTxManager</span><span class="hljs-params">(<span class="hljs-meta">@Autowired</span> SessionFactory sessionFactory)</span> </span>{<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> HibernateTransactionManager(sessionFactory);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>这两个Bean的创建都十分简单。<code>HibernateTransactionManager</code>是配合Hibernate使用声明式事务所必须的，而<code>HibernateTemplate</code>则是Spring为了便于我们使用Hibernate提供的工具类，不是非用不可，但推荐使用以简化代码。</p>
<p>到此为止，所有的配置都定义完毕，我们来看看如何将数据库表结构映射为Java对象。</p>
<p>考察如下的数据库表：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java">CREATE TABLE user<br>    id BIGINT NOT NULL AUTO_INCREMENT,<br>    <span class="hljs-function">email <span class="hljs-title">VARCHAR</span><span class="hljs-params">(<span class="hljs-number">100</span>)</span> NOT NULL,</span><br><span class="hljs-function">    password <span class="hljs-title">VARCHAR</span><span class="hljs-params">(<span class="hljs-number">100</span>)</span> NOT NULL,</span><br><span class="hljs-function">    name <span class="hljs-title">VARCHAR</span><span class="hljs-params">(<span class="hljs-number">100</span>)</span> NOT NULL,</span><br><span class="hljs-function">    createdAt BIGINT NOT NULL,</span><br><span class="hljs-function">    PRIMARY <span class="hljs-title">KEY</span> <span class="hljs-params">(`id`)</span>,</span><br><span class="hljs-function">    UNIQUE KEY `email` <span class="hljs-params">(`email`)</span></span><br><span class="hljs-function">)</span>;<br></code></pre></td></tr></tbody></table></figure>

<p>其中，<code>id</code>是自增主键，<code>email</code>、<code>password</code>、<code>name</code>是<code>VARCHAR</code>类型，<code>email</code>带唯一索引以确保唯一性，<code>createdAt</code>存储整型类型的时间戳。用JavaBean表示如下：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> </span>{<br>    <span class="hljs-keyword">private</span> Long id;<br>    <span class="hljs-keyword">private</span> String email;<br>    <span class="hljs-keyword">private</span> String password;<br>    <span class="hljs-keyword">private</span> String name;<br>    <span class="hljs-keyword">private</span> Long createdAt;<br><br>    <span class="hljs-comment">// getters and setters</span><br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>这种映射关系十分易懂，但我们需要添加一些注解来告诉Hibernate如何把<code>User</code>类映射到表记录：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Entity</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> </span>{<br>    <span class="hljs-meta">@Id</span><br>    <span class="hljs-meta">@GeneratedValue(strategy = GenerationType.IDENTITY)</span><br>    <span class="hljs-meta">@Column(nullable = false, updatable = false)</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> Long <span class="hljs-title">getId</span><span class="hljs-params">()</span> </span>{ ... }<br><br>    <span class="hljs-meta">@Column(nullable = false, unique = true, length = 100)</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getEmail</span><span class="hljs-params">()</span> </span>{ ... }<br><br>    <span class="hljs-meta">@Column(nullable = false, length = 100)</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getPassword</span><span class="hljs-params">()</span> </span>{ ... }<br><br>    <span class="hljs-meta">@Column(nullable = false, length = 100)</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getName</span><span class="hljs-params">()</span> </span>{ ... }<br><br>    <span class="hljs-meta">@Column(nullable = false, updatable = false)</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> Long <span class="hljs-title">getCreatedAt</span><span class="hljs-params">()</span> </span>{ ... }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>如果一个JavaBean被用于映射，我们就标记一个<code>@Entity</code>。默认情况下，映射的表名是<code>user</code>，如果实际的表名不同，例如实际表名是<code>users</code>，可以追加一个<code>@Table(name="users")</code>表示：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Entity</span><br><span class="hljs-meta">@Table(name="users)</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> </span>{<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>每个属性到数据库列的映射用<code>@Column()</code>标识，<code>nullable</code>指示列是否允许为<code>NULL</code>，<code>updatable</code>指示该列是否允许被用在<code>UPDATE</code>语句，<code>length</code>指示<code>String</code>类型的列的长度（如果没有指定，默认是<code>255</code>）。</p>
<p>对于主键，还需要用<code>@Id</code>标识，自增主键再追加一个<code>@GeneratedValue</code>，以便Hibernate能读取到自增主键的值。</p>
<p>细心的童鞋可能还注意到，主键<code>id</code>定义的类型不是<code>long</code>，而是<code>Long</code>。这是因为Hibernate如果检测到主键为<code>null</code>，就不会在<code>INSERT</code>语句中指定主键的值，而是返回由数据库生成的自增值，否则，Hibernate认为我们的程序指定了主键的值，会在<code>INSERT</code>语句中直接列出。<code>long</code>型字段总是具有默认值<code>0</code>，因此，每次插入的主键值总是0，导致除第一次外后续插入都将失败。</p>
<p><code>createdAt</code>虽然是整型，但我们并没有使用<code>long</code>，而是<code>Long</code>，这是因为使用基本类型会导致某种查询会添加意外的条件，后面我们会详细讨论，<strong>这里只需牢记，作为映射使用的JavaBean，所有属性都使用包装类型而不是基本类型。</strong></p>
<p> 使用Hibernate时，不要使用基本类型的属性，总是使用包装类型，如Long或Integer。</p>
<p>类似的，我们再定义一个<code>Book</code>类：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Entity</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Book</span> </span>{<br>    <span class="hljs-meta">@Id</span><br>    <span class="hljs-meta">@GeneratedValue(strategy = GenerationType.IDENTITY)</span><br>    <span class="hljs-meta">@Column(nullable = false, updatable = false)</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> Long <span class="hljs-title">getId</span><span class="hljs-params">()</span> </span>{ ... }<br><br>    <span class="hljs-meta">@Column(nullable = false, length = 100)</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getTitle</span><span class="hljs-params">()</span> </span>{ ... }<br><br>    <span class="hljs-meta">@Column(nullable = false, updatable = false)</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> Long <span class="hljs-title">getCreatedAt</span><span class="hljs-params">()</span> </span>{ ... }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>如果仔细观察<code>User</code>和<code>Book</code>，会发现它们定义的<code>id</code>、<code>createdAt</code>属性是一样的，这在数据库表结构的设计中很常见：对于每个表，通常我们会统一使用一种主键生成机制，并添加<code>createdAt</code>表示创建时间，<code>updatedAt</code>表示修改时间等通用字段。</p>
<p>不必在<code>User</code>和<code>Book</code>中重复定义这些通用字段，我们可以把它们提到一个抽象类中：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@MappedSuperclass</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AbstractEntity</span> </span>{<br><br>    <span class="hljs-keyword">private</span> Long id;<br>    <span class="hljs-keyword">private</span> Long createdAt;<br><br>    <span class="hljs-meta">@Id</span><br>    <span class="hljs-meta">@GeneratedValue(strategy = GenerationType.IDENTITY)</span><br>    <span class="hljs-meta">@Column(nullable = false, updatable = false)</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> Long <span class="hljs-title">getId</span><span class="hljs-params">()</span> </span>{ ... }<br><br>    <span class="hljs-meta">@Column(nullable = false, updatable = false)</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> Long <span class="hljs-title">getCreatedAt</span><span class="hljs-params">()</span> </span>{ ... }<br><br>    <span class="hljs-meta">@Transient</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> ZonedDateTime <span class="hljs-title">getCreatedDateTime</span><span class="hljs-params">()</span> </span>{<br>        <span class="hljs-keyword">return</span> Instant.ofEpochMilli(<span class="hljs-keyword">this</span>.createdAt).atZone(ZoneId.systemDefault());<br>    }<br><br>    <span class="hljs-meta">@PrePersist</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">preInsert</span><span class="hljs-params">()</span> </span>{<br>        setCreatedAt(System.currentTimeMillis());<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>对于<code>AbstractEntity</code>来说，我们要标注一个<code>@MappedSuperclass</code>表示它用于继承。此外，注意到我们定义了一个<code>@Transient</code>方法，它返回一个“虚拟”的属性。因为<code>getCreatedDateTime()</code>是计算得出的属性，而不是从数据库表读出的值，因此必须要标注<code>@Transient</code>，否则Hibernate会尝试从数据库读取名为<code>createdDateTime</code>这个不存在的字段从而出错。</p>
<p>再注意到<code>@PrePersist</code>标识的方法，它表示在我们将一个JavaBean持久化到数据库之前（即执行INSERT语句），Hibernate会先执行该方法，这样我们就可以自动设置好<code>createdAt</code>属性。</p>
<p>有了<code>AbstractEntity</code>，我们就可以大幅简化<code>User</code>和<code>Book</code>：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Entity</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractEntity</span> </span>{<br><br>    <span class="hljs-meta">@Column(nullable = false, unique = true, length = 100)</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getEmail</span><span class="hljs-params">()</span> </span>{ ... }<br><br>    <span class="hljs-meta">@Column(nullable = false, length = 100)</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getPassword</span><span class="hljs-params">()</span> </span>{ ... }<br><br>    <span class="hljs-meta">@Column(nullable = false, length = 100)</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getName</span><span class="hljs-params">()</span> </span>{ ... }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>注意到使用的所有注解均来自<code>javax.persistence</code>，它是JPA规范的一部分。这里我们只介绍使用注解的方式配置Hibernate映射关系，不再介绍传统的比较繁琐的XML配置。通过Spring集成Hibernate时，也不再需要<code>hibernate.cfg.xml</code>配置文件，用一句话总结：</p>
<p> <strong>使用Spring集成Hibernate，配合JPA注解，无需任何额外的XML配置。</strong></p>
<p>类似<code>User</code>、<code>Book</code>这样的用于ORM的Java Bean，我们通常称之为Entity Bean。</p>
<p>最后，我们来看看如果对<code>user</code>表进行增删改查。因为使用了Hibernate，因此，我们要做的，实际上是对<code>User</code>这个JavaBean进行“增删改查”。我们编写一个<code>UserService</code>，注入<code>HibernateTemplate</code>以便简化代码：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-meta">@Transactional</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span> </span>{<br>    <span class="hljs-meta">@Autowired</span><br>    HibernateTemplate hibernateTemplate;<br>}<br></code></pre></td></tr></tbody></table></figure>

<h3 id="Insert操作"><a href="#Insert操作" class="headerlink" title="Insert操作"></a>Insert操作</h3><p>要持久化一个<code>User</code>实例，我们只需调用<code>save()</code>方法。以<code>register()</code>方法为例，代码如下：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">register</span><span class="hljs-params">(String email, String password, String name)</span> </span>{<br>    <span class="hljs-comment">// 创建一个User对象:</span><br>    User user = <span class="hljs-keyword">new</span> User();<br>    <span class="hljs-comment">// 设置好各个属性:</span><br>    user.setEmail(email);<br>    user.setPassword(password);<br>    user.setName(name);<br>    <span class="hljs-comment">// 不要设置id，因为使用了自增主键</span><br>    <span class="hljs-comment">// 保存到数据库:</span><br>    hibernateTemplate.save(user);<br>    <span class="hljs-comment">// 现在已经自动获得了id:</span><br>    System.out.println(user.getId());<br>    <span class="hljs-keyword">return</span> user;<br>}<br></code></pre></td></tr></tbody></table></figure>

<h3 id="Delete操作"><a href="#Delete操作" class="headerlink" title="Delete操作"></a>Delete操作</h3><p>删除一个<code>User</code>相当于从表中删除对应的记录。注意Hibernate总是用<code>id</code>来删除记录，因此，要正确设置<code>User</code>的<code>id</code>属性才能正常删除记录：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">deleteUser</span><span class="hljs-params">(Long id)</span> </span>{<br>    User user = hibernateTemplate.get(User.class, id);<br>    <span class="hljs-keyword">if</span> (user != <span class="hljs-keyword">null</span>) {<br>        hibernateTemplate.delete(user);<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;<br>    }<br>    <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>通过主键删除记录时，一个常见的用法是先根据主键加载该记录，再删除。<code>load()</code>和<code>get()</code>都可以根据主键加载记录，它们的区别在于，当记录不存在时，<code>get()</code>返回<code>null</code>，而<code>load()</code>抛出异常。</p>
<h3 id="Update操作"><a href="#Update操作" class="headerlink" title="Update操作"></a>Update操作</h3><p>更新记录相当于先更新<code>User</code>的指定属性，然后调用<code>update()</code>方法：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">updateUser</span><span class="hljs-params">(Long id, String name)</span> </span>{<br>    User user = hibernateTemplate.load(User.class, id);<br>    user.setName(name);<br>    hibernateTemplate.update(user);<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>前面我们在定义<code>User</code>时，对有的属性标注了<code>@Column(updatable=false)</code>。Hibernate在更新记录时，它只会把<code>@Column(updatable=true)</code>的属性加入到<code>UPDATE</code>语句中，这样可以提供一层额外的安全性，即如果不小心修改了<code>User</code>的<code>email</code>、<code>createdAt</code>等属性，执行<code>update()</code>时并不会更新对应的数据库列。但也必须牢记：这个功能是Hibernate提供的，如果绕过Hibernate直接通过JDBC执行<code>UPDATE</code>语句仍然可以更新数据库的任意列的值。</p>
<p>最后，我们编写的大部分方法都是各种各样的查询。根据<code>id</code>查询我们可以直接调用<code>load()</code>或<code>get()</code>，如果要使用条件查询，有3种方法。</p>
<p>假设我们想执行以下查询：</p>
<figure class="highlight sql hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">FROM</span> <span class="hljs-keyword">user</span> <span class="hljs-keyword">WHERE</span> email <span class="hljs-operator">=</span> ? <span class="hljs-keyword">AND</span> password <span class="hljs-operator">=</span> ?<br></code></pre></td></tr></tbody></table></figure>

<p>我们来看看可以使用什么查询。</p>
<h3 id="使用Example查询"><a href="#使用Example查询" class="headerlink" title="使用Example查询"></a>使用Example查询</h3><p>第一种方法是使用<code>findByExample()</code>，给出一个<code>User</code>实例，Hibernate把该实例所有非<code>null</code>的属性拼成<code>WHERE</code>条件：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">login</span><span class="hljs-params">(String email, String password)</span> </span>{<br>    User example = <span class="hljs-keyword">new</span> User();<br>    example.setEmail(email);<br>    example.setPassword(password);<br>    List&lt;User&gt; list = hibernateTemplate.findByExample(example);<br>    <span class="hljs-keyword">return</span> list.isEmpty() ? <span class="hljs-keyword">null</span> : list.get(<span class="hljs-number">0</span>);<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>因为<code>example</code>实例只有<code>email</code>和<code>password</code>两个属性为非<code>null</code>，所以最终生成的<code>WHERE</code>语句就是<code>WHERE email = ? AND password = ?</code>。</p>
<p>如果我们把<code>User</code>的<code>createdAt</code>的类型从<code>Long</code>改为<code>long</code>，<code>findByExample()</code>的查询将出问题，原因在于<code>example</code>实例的<code>long</code>类型字段有了默认值0，导致Hibernate最终生成的<code>WHERE</code>语句意外变成了<code>WHERE email = ? AND password = ? AND createdAt = 0</code>。显然，额外的查询条件将导致错误的查询结果。</p>
<p> <strong>使用findByExample()时，注意基本类型字段总是会加入到WHERE条件！</strong></p>
<h3 id="使用Criteria查询"><a href="#使用Criteria查询" class="headerlink" title="使用Criteria查询"></a>使用Criteria查询</h3><p>第二种查询方法是使用Criteria查询，可以实现如下：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">login</span><span class="hljs-params">(String email, String password)</span> </span>{<br>    DetachedCriteria criteria = DetachedCriteria.forClass(User.class);<br>    criteria.add(Restrictions.eq(<span class="hljs-string">"email"</span>, email))<br>            .add(Restrictions.eq(<span class="hljs-string">"password"</span>, password));<br>    List&lt;User&gt; list = (List&lt;User&gt;) hibernateTemplate.findByCriteria(criteria);<br>    <span class="hljs-keyword">return</span> list.isEmpty() ? <span class="hljs-keyword">null</span> : list.get(<span class="hljs-number">0</span>);<br>}<br></code></pre></td></tr></tbody></table></figure>

<p><code>DetachedCriteria</code>使用链式语句来添加多个<code>AND</code>条件。和<code>findByExample()</code>相比，<code>findByCriteria()</code>可以组装出更灵活的<code>WHERE</code>条件，例如：</p>
<figure class="highlight sql hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">FROM</span> <span class="hljs-keyword">user</span> <span class="hljs-keyword">WHERE</span> (email <span class="hljs-operator">=</span> ? <span class="hljs-keyword">OR</span> name <span class="hljs-operator">=</span> ?) <span class="hljs-keyword">AND</span> password <span class="hljs-operator">=</span> ?<br></code></pre></td></tr></tbody></table></figure>

<p>上述查询没法用<code>findByExample()</code>实现，但用Criteria查询可以实现如下：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java">DetachedCriteria criteria = DetachedCriteria.forClass(User.class);<br>criteria.add(<br>    Restrictions.and(<br>        Restrictions.or(<br>            Restrictions.eq(<span class="hljs-string">"email"</span>, email),<br>            Restrictions.eq(<span class="hljs-string">"name"</span>, email)<br>        ),<br>		Restrictions.eq(<span class="hljs-string">"password"</span>, password)<br>    )<br>);<br></code></pre></td></tr></tbody></table></figure>

<p>只要组织好<code>Restrictions</code>的嵌套关系，Criteria查询可以实现任意复杂的查询。</p>
<h3 id="使用HQL查询"><a href="#使用HQL查询" class="headerlink" title="使用HQL查询"></a>使用HQL查询</h3><p>最后一种常用的查询是直接编写Hibernate内置的HQL查询：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;User&gt; list = (List&lt;User&gt;) hibernateTemplate.find(<span class="hljs-string">"FROM User WHERE email=? AND password=?"</span>, email, password);<br></code></pre></td></tr></tbody></table></figure>

<p>和SQL相比，HQL使用类名和属性名，由Hibernate自动转换为实际的表名和列名。详细的HQL语法可以参考<a target="_blank" rel="noopener" href="https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#hql">Hibernate文档</a>。</p>
<p>除了可以直接传入HQL字符串外，Hibernate还可以使用一种<code>NamedQuery</code>，它给查询起个名字，然后保存在注解中。使用<code>NamedQuery</code>时，我们要先在<code>User</code>类标注：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@NamedQueries(</span><br><span class="hljs-meta">    @NamedQuery(</span><br><span class="hljs-meta">        // 查询名称:</span><br><span class="hljs-meta">        name = "login",</span><br><span class="hljs-meta">        // 查询语句:</span><br><span class="hljs-meta">        query = "SELECT u FROM User u WHERE u.email=?0 AND u.password=?1"</span><br><span class="hljs-meta">    )</span><br><span class="hljs-meta">)</span><br><span class="hljs-meta">@Entity</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractEntity</span> </span>{<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>注意到引入的NamedQuery是<code>javax.persistence.NamedQuery</code>，它和直接传入HQL有点不同的是，占位符使用<code>?0</code>、<code>?1</code>，并且索引是从<code>0</code>开始的（真乱）。</p>
<p>使用<code>NamedQuery</code>只需要引入查询名和参数：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">login</span><span class="hljs-params">(String email, String password)</span> </span>{<br>    List&lt;User&gt; list = (List&lt;User&gt;) hibernateTemplate.findByNamedQuery(<span class="hljs-string">"login"</span>, email, password);<br>    <span class="hljs-keyword">return</span> list.isEmpty() ? <span class="hljs-keyword">null</span> : list.get(<span class="hljs-number">0</span>);<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>直接写HQL和使用<code>NamedQuery</code>各有优劣。前者可以在代码中直观地看到查询语句，后者可以在<code>User</code>类统一管理所有相关查询。</p>
<h3 id="使用Hibernate原生接口"><a href="#使用Hibernate原生接口" class="headerlink" title="使用Hibernate原生接口"></a>使用Hibernate原生接口</h3><p>如果要使用Hibernate原生接口，但不知道怎么写，可以参考<code>HibernateTemplate</code>的源码。使用Hibernate的原生接口实际上总是从<code>SessionFactory</code>出发，它通常用全局变量存储，在<code>HibernateTemplate</code>中以成员变量被注入。有了<code>SessionFactory</code>，使用Hibernate用法如下：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">operation</span><span class="hljs-params">()</span> </span>{<br>    Session session = <span class="hljs-keyword">null</span>;<br>    <span class="hljs-keyword">boolean</span> isNew = <span class="hljs-keyword">false</span>;<br>    <span class="hljs-comment">// 获取当前Session或者打开新的Session:</span><br>    <span class="hljs-keyword">try</span> {<br>        session = <span class="hljs-keyword">this</span>.sessionFactory.getCurrentSession();<br>    } <span class="hljs-keyword">catch</span> (HibernateException e) {<br>        session = <span class="hljs-keyword">this</span>.sessionFactory.openSession();<br>        isNew = <span class="hljs-keyword">true</span>;<br>    }<br>    <span class="hljs-comment">// 操作Session:</span><br>    <span class="hljs-keyword">try</span> {<br>        User user = session.load(User.class, <span class="hljs-number">123L</span>);<br>    }<br>    <span class="hljs-keyword">finally</span> {<br>        <span class="hljs-comment">// 关闭新打开的Session:</span><br>        <span class="hljs-keyword">if</span> (isNew) {<br>            session.close();<br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<h2 id="集成JPA"><a href="#集成JPA" class="headerlink" title="集成JPA"></a>集成JPA</h2><p>在讨论JPA（Java Persistence API）之前，我们要注意到JavaEE早在1999年就发布了，并有Servlet、JMS等诸多标准。和其他平台不同，Java世界早期热衷于标准先行，大家先坐下来把接口定了，然后，各自回家去实现接口。这样，用户就可以在不同厂家提供的产品进行选择，还可以随意切换，因为用户编写代码时只需要引用接口，不需要引用具体的底层实现，这一点很类似JDBC。</p>
<p>JPA就是JavaEE的一个ORM标准，它的实现其实和Hibernate没啥本质区别，但是如果用户使用JPA，引用的就是<code>javax.persistance</code>这个包，而不是<code>org.hibernate</code>这样的第三方包。</p>
<p>我们使用JPA时也完全可以选择Hibernate作为底层实现，但也可以选择其他产品，比如EclipseLink。Spring内置了JPA的集成，并支持选择Hibernate或EclipseLink作为实现。这里我们仍然以主流的Hibernate作为JPA实现的例子，演示JPA的基本用法。</p>
<p>和使用Hibernate一样，我们只需要引入如下依赖：</p>
<ul>
<li>org.springframework:spring-context:5.2.0.RELEASE</li>
<li>org.springframework:spring-orm:5.2.0.RELEASE</li>
<li>javax.annotation:javax.annotation-api:1.3.2</li>
<li>org.hibernate:hibernate-core:5.4.2.Final</li>
<li>com.zaxxer:HikariCP:3.4.2</li>
<li>org.hsqldb:hsqldb:2.5.0</li>
</ul>
<p>然后，在<code>AppConfig</code>中启用声明式事务管理，创建<code>DataSource</code>：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Configuration</span><br><span class="hljs-meta">@ComponentScan</span><br><span class="hljs-meta">@EnableTransactionManagement</span><br><span class="hljs-meta">@PropertySource("jdbc.properties")</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppConfig</span> </span>{<br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-function">DataSource <span class="hljs-title">createDataSource</span><span class="hljs-params">()</span> </span>{ ... }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>使用Hibernate时，我们需要创建一个<code>LocalSessionFactoryBean</code>，并让它再自动创建一个<code>SessionFactory</code>。使用JPA也是类似的，我们需要创建一个<code>LocalContainerEntityManagerFactoryBean</code>，并让它再自动创建一个<code>EntityManagerFactory</code>：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Bean</span><br><span class="hljs-function">LocalContainerEntityManagerFactoryBean <span class="hljs-title">createEntityManagerFactory</span><span class="hljs-params">(<span class="hljs-meta">@Autowired</span> DataSource dataSource)</span> </span>{<br>    <span class="hljs-keyword">var</span> entityManagerFactoryBean = <span class="hljs-keyword">new</span> LocalContainerEntityManagerFactoryBean();<br>    <span class="hljs-comment">// 设置DataSource:</span><br>    entityManagerFactoryBean.setDataSource(dataSource);<br>    <span class="hljs-comment">// 扫描指定的package获取所有entity class:</span><br>    entityManagerFactoryBean.setPackagesToScan(<span class="hljs-string">"com.itranswarp.learnjava.entity"</span>);<br>    <span class="hljs-comment">// 指定JPA的提供商是Hibernate:</span><br>    JpaVendorAdapter vendorAdapter = <span class="hljs-keyword">new</span> HibernateJpaVendorAdapter();<br>    entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter);<br>    <span class="hljs-comment">// 设定特定提供商自己的配置:</span><br>    <span class="hljs-keyword">var</span> props = <span class="hljs-keyword">new</span> Properties();<br>    props.setProperty(<span class="hljs-string">"hibernate.hbm2ddl.auto"</span>, <span class="hljs-string">"update"</span>);<br>    props.setProperty(<span class="hljs-string">"hibernate.dialect"</span>, <span class="hljs-string">"org.hibernate.dialect.HSQLDialect"</span>);<br>    props.setProperty(<span class="hljs-string">"hibernate.show_sql"</span>, <span class="hljs-string">"true"</span>);<br>    entityManagerFactoryBean.setJpaProperties(props);<br>    <span class="hljs-keyword">return</span> entityManagerFactoryBean;<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>观察上述代码，除了需要注入<code>DataSource</code>和设定自动扫描的<code>package</code>外，还需要指定JPA的提供商，这里使用Spring提供的一个<code>HibernateJpaVendorAdapter</code>，最后，针对Hibernate自己需要的配置，以<code>Properties</code>的形式注入。</p>
<p>最后，我们还需要实例化一个<code>JpaTransactionManager</code>，以实现声明式事务：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Bean</span><br><span class="hljs-function">PlatformTransactionManager <span class="hljs-title">createTxManager</span><span class="hljs-params">(<span class="hljs-meta">@Autowired</span> EntityManagerFactory entityManagerFactory)</span> </span>{<br>    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> JpaTransactionManager(entityManagerFactory);<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>这样，我们就完成了JPA的全部初始化工作。有些童鞋可能从网上搜索得知JPA需要<code>persistence.xml</code>配置文件，以及复杂的<code>orm.xml</code>文件。这里我们负责地告诉大家，使用Spring+Hibernate作为JPA实现，无需任何配置文件。</p>
<p>所有Entity Bean的配置和上一节完全相同，全部采用Annotation标注。我们现在只需关心具体的业务类如何通过JPA接口操作数据库。</p>
<p>还是以<code>UserService</code>为例，除了标注<code>@Component</code>和<code>@Transactional</code>外，我们需要注入一个<code>EntityManager</code>，但是不要使用<code>Autowired</code>，而是<code>@PersistenceContext</code>：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-meta">@Transactional</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span> </span>{<br>    <span class="hljs-meta">@PersistenceContext</span><br>    EntityManager em;<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>我们回顾一下JDBC、Hibernate和JPA提供的接口，实际上，它们的关系如下：</p>
<table>
<thead>
<tr>
<th align="left">JDBC</th>
<th align="left">Hibernate</th>
<th align="left">JPA</th>
</tr>
</thead>
<tbody><tr>
<td align="left">DataSource</td>
<td align="left">SessionFactory</td>
<td align="left">EntityManagerFactory</td>
</tr>
<tr>
<td align="left">Connection</td>
<td align="left">Session</td>
<td align="left">EntityManager</td>
</tr>
</tbody></table>
<p><code>SessionFactory</code>和<code>EntityManagerFactory</code>相当于<code>DataSource</code>，<code>Session</code>和<code>EntityManager</code>相当于<code>Connection</code>。每次需要访问数据库的时候，需要获取新的<code>Session</code>和<code>EntityManager</code>，用完后再关闭。</p>
<p>但是，注意到<code>UserService</code>注入的不是<code>EntityManagerFactory</code>，而是<code>EntityManager</code>，并且标注了<code>@PersistenceContext</code>。难道使用JPA可以允许多线程操作同一个<code>EntityManager</code>？</p>
<p>实际上这里注入的并不是真正的<code>EntityManager</code>，而是一个<code>EntityManager</code>的代理类，相当于：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EntityManagerProxy</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">EntityManager</span> </span>{<br>    <span class="hljs-keyword">private</span> EntityManagerFactory emf;<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>Spring遇到标注了<code>@PersistenceContext</code>的<code>EntityManager</code>会自动注入代理，该代理会在必要的时候自动打开<code>EntityManager</code>。换句话说，多线程引用的<code>EntityManager</code>虽然是同一个代理类，但该代理类内部针对不同线程会创建不同的<code>EntityManager</code>实例。</p>
<p>简单总结一下，标注了<code>@PersistenceContext</code>的<code>EntityManager</code>可以被多线程安全地共享。</p>
<p>因此，在<code>UserService</code>的每个业务方法里，直接使用<code>EntityManager</code>就很方便。以主键查询为例：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">getUserById</span><span class="hljs-params">(<span class="hljs-keyword">long</span> id)</span> </span>{<br>    User user = <span class="hljs-keyword">this</span>.em.find(User.class, id);<br>    <span class="hljs-keyword">if</span> (user == <span class="hljs-keyword">null</span>) {<br>        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(<span class="hljs-string">"User not found by id: "</span> + id);<br>    }<br>    <span class="hljs-keyword">return</span> user;<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>JPA同样支持Criteria查询，比如我们需要的查询如下：</p>
<figure class="highlight sql hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">FROM</span> <span class="hljs-keyword">user</span> <span class="hljs-keyword">WHERE</span> email <span class="hljs-operator">=</span> ?<br></code></pre></td></tr></tbody></table></figure>

<p>使用Criteria查询的代码如下：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">fetchUserByEmail</span><span class="hljs-params">(String email)</span> </span>{<br>    <span class="hljs-comment">// CriteriaBuilder:</span><br>    <span class="hljs-keyword">var</span> cb = em.getCriteriaBuilder();<br>    CriteriaQuery&lt;User&gt; q = cb.createQuery(User.class);<br>    Root&lt;User&gt; r = q.from(User.class);<br>    q.where(cb.equal(r.get(<span class="hljs-string">"email"</span>), cb.parameter(String.class, <span class="hljs-string">"e"</span>)));<br>    TypedQuery&lt;User&gt; query = em.createQuery(q);<br>    <span class="hljs-comment">// 绑定参数:</span><br>    query.setParameter(<span class="hljs-string">"e"</span>, email);<br>    <span class="hljs-comment">// 执行查询:</span><br>    List&lt;User&gt; list = query.getResultList();<br>    <span class="hljs-keyword">return</span> list.isEmpty() ? <span class="hljs-keyword">null</span> : list.get(<span class="hljs-number">0</span>);<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>一个简单的查询用Criteria写出来就像上面那样复杂，太恐怖了，如果条件多加几个，这种写法谁读得懂？</p>
<p>所以，正常人还是建议写JPQL查询，它的语法和HQL基本差不多：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">getUserByEmail</span><span class="hljs-params">(String email)</span> </span>{<br>    <span class="hljs-comment">// JPQL查询:</span><br>    TypedQuery&lt;User&gt; query = em.createQuery(<span class="hljs-string">"SELECT u FROM User u WHERE u.email = :e"</span>, User.class);<br>    query.setParameter(<span class="hljs-string">"e"</span>, email);<br>    List&lt;User&gt; list = query.getResultList();<br>    <span class="hljs-keyword">if</span> (list.isEmpty()) {<br>        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(<span class="hljs-string">"User not found by email."</span>);<br>    }<br>    <span class="hljs-keyword">return</span> list.get(<span class="hljs-number">0</span>);<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>同样的，JPA也支持NamedQuery，即先给查询起个名字，再按名字创建查询：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">login</span><span class="hljs-params">(String email, String password)</span> </span>{<br>    TypedQuery&lt;User&gt; query = em.createNamedQuery(<span class="hljs-string">"login"</span>, User.class);<br>    query.setParameter(<span class="hljs-string">"e"</span>, email);<br>    query.setParameter(<span class="hljs-string">"p"</span>, password);<br>    List&lt;User&gt; list = query.getResultList();<br>    <span class="hljs-keyword">return</span> list.isEmpty() ? <span class="hljs-keyword">null</span> : list.get(<span class="hljs-number">0</span>);<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>NamedQuery通过注解标注在<code>User</code>类上，它的定义和上一节的<code>User</code>类一样：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@NamedQueries(</span><br><span class="hljs-meta">    @NamedQuery(</span><br><span class="hljs-meta">        name = "login",</span><br><span class="hljs-meta">        query = "SELECT u FROM User u WHERE u.email=:e AND u.password=:p"</span><br><span class="hljs-meta">    )</span><br><span class="hljs-meta">)</span><br><span class="hljs-meta">@Entity</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> </span>{<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>对数据库进行增删改的操作，可以分别使用<code>persist()</code>、<code>remove()</code>和<code>merge()</code>方法，参数均为Entity Bean本身，使用非常简单，这里不再多述。</p>
<h2 id="集成Mybatis"><a href="#集成Mybatis" class="headerlink" title="集成Mybatis"></a>集成Mybatis</h2><p>使用Hibernate或JPA操作数据库时，这类ORM干的主要工作就是把ResultSet的每一行变为Java Bean，或者把Java Bean自动转换到INSERT或UPDATE语句的参数中，从而实现ORM。而ORM框架之所以知道如何把行数据映射到Java Bean，是因为我们在Java Bean的属性上给了足够的注解作为元数据，ORM框架获取Java Bean的注解后，就知道如何进行双向映射。</p>
<p>那么，ORM是如何跟踪Java Bean的修改，以便在<code>update()</code>操作中更新必要的属性？</p>
<p>答案是使用Proxy模式，从ORM框架读取到的是User实例实际上并不是User类，而是代理类，代理类继承自User类，但针对每个<code>setter</code>方法做了覆写。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserProxy</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">User</span> </span>{<br>    <span class="hljs-keyword">boolean</span> _isNameChanged;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setName</span><span class="hljs-params">(String name)</span> </span>{<br>        <span class="hljs-keyword">super</span>.setName(name);<br>        _isNameChanged = <span class="hljs-keyword">true</span>;<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>这样，代理类就能跟踪到每个属性的变化。针对一对多或多对一关系时，代理类可以直接通过<code>getter</code>方法查询数据库。</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserProxy</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">User</span> </span>{<br>    Session _session;<br>    <span class="hljs-keyword">boolean</span> _isNameChanged;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setName</span><span class="hljs-params">(String name)</span> </span>{<br>        <span class="hljs-keyword">super</span>.setName(name);<br>        _isNameChanged = <span class="hljs-keyword">true</span>;<br>    }<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 获取User对象关联的Address对象:</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> Address <span class="hljs-title">getAddress</span><span class="hljs-params">()</span> </span>{<br>        Query q = _session.createQuery(<span class="hljs-string">"from Address where userId = :userId"</span>);<br>        q.setParameter(<span class="hljs-string">"userId"</span>, <span class="hljs-keyword">this</span>.getId());<br>        List&lt;Address&gt; list = query.list();<br>        <span class="hljs-keyword">return</span> list.isEmpty() ? <span class="hljs-keyword">null</span> : list(<span class="hljs-number">0</span>);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>为了实现这样的查询，UserProxy必须保存Hibernate的当前Session。但是，当事务提交后，Session自动关闭，此时再获取<code>getAddress()</code>将无法访问数据库，或者获取的不是事务一致的数据。因此，ORM框架总是引入了Attached/Detached状态，表示当前此Java Bean到底是在Session的范围内，还是脱离了Session变成了一个“游离”对象。很多初学者无法正确理解状态变化和事务边界，就会造成大量的<code>PersistentObjectException</code>异常。这种隐式状态使得普通Java Bean的生命周期变得复杂。</p>
<p>此外，Hibernate和JPA为了实现兼容多种数据库，它使用HQL或JPQL查询，经过一道转换，变成特定数据库的SQL，理论上这样可以做到无缝切换数据库，但这一层自动转换除了少许的性能开销外，给SQL级别的优化带来了麻烦。</p>
<p>最后，ORM框架通常提供了缓存，并且还分为一级缓存和二级缓存。一级缓存是指在一个Session范围内的缓存，常见的情景是根据主键查询时，两次查询可以返回同一实例：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">User user1 = session.load(User.class, <span class="hljs-number">123</span>);<br>User user2 = session.load(User.class, <span class="hljs-number">123</span>);<br></code></pre></td></tr></tbody></table></figure>

<p>二级缓存是指跨Session的缓存，一般默认关闭，需要手动配置。二级缓存极大的增加了数据的不一致性，原因在于SQL非常灵活，常常会导致意外的更新。例如：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 线程1读取:</span><br>User user1 = session1.load(User.class, <span class="hljs-number">123</span>);<br>...<br><span class="hljs-comment">// 一段时间后，线程2读取:</span><br>User user2 = session2.load(User.class, <span class="hljs-number">123</span>);<br></code></pre></td></tr></tbody></table></figure>

<p>当二级缓存生效的时候，两个线程读取的User实例是一样的，但是，数据库对应的行记录完全可能被修改，例如：</p>
<figure class="highlight sql hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-comment">-- 给老用户增加100积分:</span><br>UPDATE users <span class="hljs-keyword">SET</span> bonus <span class="hljs-operator">=</span> bonus <span class="hljs-operator">+</span> <span class="hljs-number">100</span> <span class="hljs-keyword">WHERE</span> createdAt <span class="hljs-operator">&lt;=</span> ?<br></code></pre></td></tr></tbody></table></figure>

<p>ORM无法判断<code>id=123</code>的用户是否受该<code>UPDATE</code>语句影响。考虑到数据库通常会支持多个应用程序，此UPDATE语句可能由其他进程执行，ORM框架就更不知道了。</p>
<p>我们把这种ORM框架称之为全自动ORM框架，对比Spring提供的JdbcTemplate，主要有以下几点差别：</p>
<ol>
<li>查询后需要手动提供Mapper实例以便把ResultSet的每一行变为Java对象</li>
<li>增删改操作所需的参数列表，需要手动传入，即把User实例变为[user.id, user.name, user.email]这样的列表，比较麻烦</li>
</ol>
<p>但是JdbcTemplate的优势在于它的确定性：即每次读操作一定是数据库操作而不是缓存，所执行的SQL是完全确定的，缺点是代码比较繁琐，构造<code>INSERT INTO users VALUES (?,?,?)</code>更是复杂。</p>
<p>所以，介于全自动ORM如Hibernate和手写全部如JdbcTemplate之间，还有一种半自动ORM，它只负责把ResultSet自动映射到Java Bean，或者自动填充Java Bean参数，但仍需要自己写出SQL。MyBatis就是这样一种半自动化框架。</p>
<p>我们来看看如何在Spring中集成MyBatis。</p>
<p>首先，我们要引入MyBatis本身，其次，由于Spring并没有内置对MyBatis的集成，所以，我们需要再引入MyBatis官方自己开发的一个与Spring集成的库：</p>
<ul>
<li>org.mybatis:mybatis:3.5.4</li>
<li>org.mybatis:mybatis-spring:2.0.4</li>
</ul>
<p>和前面一样，先创建DataSource：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Configuration</span><br><span class="hljs-meta">@ComponentScan</span><br><span class="hljs-meta">@EnableTransactionManagement</span><br><span class="hljs-meta">@PropertySource("jdbc.properties")</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppConfig</span> </span>{<br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-function">DataSource <span class="hljs-title">createDataSource</span><span class="hljs-params">()</span> </span>{ ... }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>再回顾一下Hibernate和JPA的<code>SessionFactory</code>与<code>EntityManagerFactory</code>，MyBatis与之对应的是<code>SqlSessionFactory</code>和<code>SqlSession</code>：</p>
<table>
<thead>
<tr>
<th align="left">JDBC</th>
<th align="left">Hibernate</th>
<th align="left">JPA</th>
<th align="left">MyBatis</th>
</tr>
</thead>
<tbody><tr>
<td align="left">DataSource</td>
<td align="left">SessionFactory</td>
<td align="left">EntityManagerFactory</td>
<td align="left">SqlSessionFactory</td>
</tr>
<tr>
<td align="left">Connection</td>
<td align="left">Session</td>
<td align="left">EntityManager</td>
<td align="left">SqlSession</td>
</tr>
</tbody></table>
<p>可见，ORM的设计套路都是类似的，使用MyBatis的核心是创建<code>SqlSessionFactory</code>，这里我们需要创建的是<code>SqlSessionFactoryBean</code>：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Bean</span><br><span class="hljs-function">SqlSessionFactoryBean <span class="hljs-title">createSqlSessionFactoryBean</span><span class="hljs-params">(<span class="hljs-meta">@Autowired</span> DataSource dataSource)</span> </span>{<br>    <span class="hljs-keyword">var</span> sqlSessionFactoryBean = <span class="hljs-keyword">new</span> SqlSessionFactoryBean();<br>    sqlSessionFactoryBean.setDataSource(dataSource);<br>    <span class="hljs-keyword">return</span> sqlSessionFactoryBean;<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>MyBatis可以直接使用Spring管理的声明式事务，因此，创建事务管理器和使用JDBC是一样的：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Bean</span><br><span class="hljs-function">PlatformTransactionManager <span class="hljs-title">createTxManager</span><span class="hljs-params">(<span class="hljs-meta">@Autowired</span> DataSource dataSource)</span> </span>{<br>    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> DataSourceTransactionManager(dataSource);<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>和Hibernate不同的是，MyBatis使用Mapper来实现映射，而且Mapper必须是接口。我们以User类为例，在User类和users表之间映射的UserMapper编写如下：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">UserMapper</span> </span>{<br>	<span class="hljs-meta">@Select("SELECT * FROM users WHERE id = #{id}")</span><br>	<span class="hljs-function">User <span class="hljs-title">getById</span><span class="hljs-params">(<span class="hljs-meta">@Param("id")</span> <span class="hljs-keyword">long</span> id)</span></span>;<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>注意：这里的Mapper不是JdbcTemplate的RowMapper的概念，它是定义访问users表的接口方法。比如我们定义了一个<code>User getById(long)</code>的主键查询方法，不仅要定义接口方法本身，还要明确写出查询的SQL，这里用注解<code>@Select</code>标记。SQL语句的任何参数，都与方法参数按名称对应。例如，方法参数id的名字通过注解<code>@Param()</code>标记为<code>id</code>，则SQL语句里将来替换的占位符就是<code>#{id}</code>。</p>
<p>如果有多个参数，那么每个参数命名后直接在SQL中写出对应的占位符即可：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Select("SELECT * FROM users LIMIT #{offset}, #{maxResults}")</span><br><span class="hljs-function">List&lt;User&gt; <span class="hljs-title">getAll</span><span class="hljs-params">(<span class="hljs-meta">@Param("offset")</span> <span class="hljs-keyword">int</span> offset, <span class="hljs-meta">@Param("maxResults")</span> <span class="hljs-keyword">int</span> maxResults)</span></span>;<br></code></pre></td></tr></tbody></table></figure>

<p>注意：MyBatis执行查询后，将根据方法的返回类型自动把ResultSet的每一行转换为User实例，转换规则是按列名和属性名对应。如果列名和属性名不同，最简单的方式是编写SELECT语句的别名。</p>
<figure class="highlight sql hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-comment">-- 列名是created_time，属性名是createdAt:</span><br><span class="hljs-keyword">SELECT</span> id, name, email, created_time <span class="hljs-keyword">AS</span> createdAt <span class="hljs-keyword">FROM</span> users<br></code></pre></td></tr></tbody></table></figure>

<p>执行INSERT语句就稍微麻烦点，因为我们希望传入User实例，因此，定义的方法接口与<code>@Insert</code>注解如下：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Insert("INSERT INTO users (email, password, name, createdAt) VALUES (#{user.email}, #{user.password}, #{user.name}, #{user.createdAt})")</span><br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">insert</span><span class="hljs-params">(<span class="hljs-meta">@Param("user")</span> User user)</span></span>;<br></code></pre></td></tr></tbody></table></figure>

<p>上述方法传入的参数名称是<code>user</code>，参数类型是User类，在SQL中引用的时候，以<code>#{obj.property}</code>的方式写占位符。和Hibernate这样的全自动化ORM相比，MyBatis必须写出完整的INSERT语句。</p>
<p>如果<code>users</code>表的id是自增主键，那么，我们在SQL中不传入id，但希望获取插入后的主键，需要再加一个<code>@Options</code>注解：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")</span><br><span class="hljs-meta">@Insert("INSERT INTO users (email, password, name, createdAt) VALUES (#{user.email}, #{user.password}, #{user.name}, #{user.createdAt})")</span><br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">insert</span><span class="hljs-params">(<span class="hljs-meta">@Param("user")</span> User user)</span></span>;<br></code></pre></td></tr></tbody></table></figure>

<p><code>keyProperty</code>和<code>keyColumn</code>分别指出JavaBean的属性和数据库的主键列名。</p>
<p>执行UPDATE和DELETE语句相对比较简单，我们定义方法如下：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Update("UPDATE users SET name = #{user.name}, createdAt = #{user.createdAt} WHERE id = #{user.id}")</span><br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">update</span><span class="hljs-params">(<span class="hljs-meta">@Param("user")</span> User user)</span></span>;<br><br><span class="hljs-meta">@Delete("DELETE FROM users WHERE id = #{id}")</span><br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">deleteById</span><span class="hljs-params">(<span class="hljs-meta">@Param("id")</span> <span class="hljs-keyword">long</span> id)</span></span>;<br></code></pre></td></tr></tbody></table></figure>

<p>有了<code>UserMapper</code>接口，还需要对应的实现类才能真正执行这些数据库操作的方法。虽然可以自己写实现类，但我们除了编写<code>UserMapper</code>接口外，还有<code>BookMapper</code>、<code>BonusMapper</code>……一个一个写太麻烦，因此，MyBatis提供了一个<code>MapperFactoryBean</code>来自动创建所有Mapper的实现类。可以用一个简单的注解来启用它：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@MapperScan("com.itranswarp.learnjava.mapper")</span><br>...其他注解...<br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppConfig</span> </span>{<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>有了<code>@MapperScan</code>，就可以让MyBatis自动扫描指定包的所有Mapper并创建实现类。在真正的业务逻辑中，我们可以直接注入：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-meta">@Transactional</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span> </span>{<br>    <span class="hljs-comment">// 注入UserMapper:</span><br>    <span class="hljs-meta">@Autowired</span><br>    UserMapper userMapper;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">getUserById</span><span class="hljs-params">(<span class="hljs-keyword">long</span> id)</span> </span>{<br>        <span class="hljs-comment">// 调用Mapper方法:</span><br>        User user = userMapper.getById(id);<br>        <span class="hljs-keyword">if</span> (user == <span class="hljs-keyword">null</span>) {<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(<span class="hljs-string">"User not found by id."</span>);<br>        }<br>        <span class="hljs-keyword">return</span> user;<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>可见，业务逻辑主要就是通过<code>XxxMapper</code>定义的数据库方法来访问数据库。</p>
<h4 id="XML配置"><a href="#XML配置" class="headerlink" title="XML配置"></a>XML配置</h4><p>上述Spring集成MyBatis的方式，我们只需要用到注解，并没有使用任何任何XML配置文件。MyBatis也允许使用XML配置映射关系和SQL语句，例如，更新<code>User</code>时根据属性值构造动态SQL：</p>
<figure class="highlight xml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">update</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"updateUser"</span>&gt;</span><br>  UPDATE users SET<br>  <span class="hljs-tag">&lt;<span class="hljs-name">set</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">if</span> <span class="hljs-attr">test</span>=<span class="hljs-string">"user.name != null"</span>&gt;</span> name = #{user.name} <span class="hljs-tag">&lt;/<span class="hljs-name">if</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">if</span> <span class="hljs-attr">test</span>=<span class="hljs-string">"user.hobby != null"</span>&gt;</span> hobby = #{user.hobby} <span class="hljs-tag">&lt;/<span class="hljs-name">if</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">if</span> <span class="hljs-attr">test</span>=<span class="hljs-string">"user.summary != null"</span>&gt;</span> summary = #{user.summary} <span class="hljs-tag">&lt;/<span class="hljs-name">if</span>&gt;</span><br>  <span class="hljs-tag">&lt;/<span class="hljs-name">set</span>&gt;</span><br>  WHERE id = #{user.id}<br><span class="hljs-tag">&lt;/<span class="hljs-name">update</span>&gt;</span><br></code></pre></td></tr></tbody></table></figure>

<p>编写XML配置的优点是可以组装出动态SQL，并且把所有SQL操作集中在一起，缺点是配置起来太繁琐，调用方法时如果想查看SQL还需要定位到XML配置中。这里我们不介绍XML的配置方式，需要了解的童鞋请自行阅读<a target="_blank" rel="noopener" href="https://mybatis.org/mybatis-3/zh/configuration.html">官方文档</a>。</p>
<p>使用MyBatis最大的问题是所有SQL都需要全部手写，优点是执行的SQL就是我们自己写的SQL，对SQL进行优化非常简单，也可以编写任意复杂的SQL，或者使用数据库的特定语法，但切换数据库可能就不太容易。好消息是大部分项目并没有切换数据库的需求，完全可以针对某个数据库编写尽可能优化的SQL。</p>
<h2 id="设计ORM"><a href="#设计ORM" class="headerlink" title="设计ORM"></a>设计ORM</h2><p>ORM框架就是自动映射数据库表结构到Java Bean的工具，设计并实现一个简单高效的ORM框架并不难。</p>
</body></html>
    
    </div>
    
    
    <div class="columns is-mobile is-multiline article-nav">
        <span class="column is-12-mobile is-half-desktop  article-nav-prev">
            
            <a href="/Study/Java/Spring/%E5%BC%80%E5%8F%91Web%E5%BA%94%E7%94%A8/">开发Web应用</a>
            
        </span>
        <span class="column is-12-mobile is-half-desktop  article-nav-next">
            
            <a href="/Study/Java/Spring/%E4%BD%BF%E7%94%A8AOP/">使用AOP</a>
            
        </span>
    </div>
    
</article>


<div class="sharebox">
    
<div class="sharethis-inline-share-buttons"></div>
<script type='text/javascript' src='//platform-api.sharethis.com/js/sharethis.js#property=608c1408daac690012507aa2&amp;product=sop' async='async'></script>

</div>



    </div>
</section>
    <footer class="footer">
    <div class="container">
        <div class="columns content">
            <div class="column is-narrow has-text-centered">
                &copy; 2021 wanzixin&nbsp;
                Powered by <a href="http://hexo.io/" target="_blank">Hexo</a> & <a
                        target="_blank" rel="noopener" href="http://github.com/ppoffice/hexo-theme-minos">Minos</a>
            </div>
            <div class="column is-hidden-mobile"></div>

            
            <div class="column is-narrow">
                <div class="columns is-mobile is-multiline is-centered">
                
                    
                <a class="column is-narrow has-text-black" title="GitHub" target="_blank" rel="noopener" href="https://github.com/ppoffice/hexo-theme-minos">
                    
                    GitHub
                    
                </a>
                
                </div>
            </div>
            
            
<div class="column is-narrow has-text-centered">
    <div class="dropdown is-up is-right is-hoverable" style="margin-top: -0.2em;">
        <div class="dropdown-trigger">
            <button class="button is-small" aria-haspopup="true" aria-controls="dropdown-menu7">
                <span class="icon">
                    <i class="fas fa-globe"></i>
                </span>
                <span>English</span>
                <span class="icon is-small">
            <i class="fas fa-angle-down" aria-hidden="true"></i>
          </span>
            </button>
        </div>
        <div class="dropdown-menu has-text-left" role="menu">
            <div class="dropdown-content">
            
                <a href="/Study/Java/Spring/%E8%AE%BF%E9%97%AE%E6%95%B0%E6%8D%AE%E5%BA%93/" class="dropdown-item">
                    English
                </a>
            
                <a href="/zh-cn/Study/Java/Spring/%E8%AE%BF%E9%97%AE%E6%95%B0%E6%8D%AE%E5%BA%93/" class="dropdown-item">
                    简体中文
                </a>
            
            </div>
        </div>
    </div>
</div>

        </div>
    </div>
</footer>
    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment-with-locales.min.js"></script>

<!-- test if the browser is outdated -->
<div id="outdated">
    <h6>Your browser is out-of-date!</h6>
    <p>Update your browser to view this website correctly. <a id="btnUpdateBrowser" target="_blank" rel="noopener" href="http://outdatedbrowser.com/">Update my browser now </a></p>
    <p class="last"><a href="#" id="btnCloseUpdateBrowser" title="Close">&times;</a></p>
</div>
<script src="//cdnjs.cloudflare.com/ajax/libs/outdated-browser/1.1.5/outdatedbrowser.min.js"></script>
<script>
    $(document).ready(function () {
        // plugin function, place inside DOM ready function
        outdatedBrowser({
            bgColor: '#f25648',
            color: '#ffffff',
            lowerThan: 'flex'
        })
    });
</script>

<script>
    window.FontAwesomeConfig = {
        searchPseudoElements: true
    }
    moment.locale("en-AU");
</script>


    
    
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js?config=TeX-MML-AM_CHTML"></script>
<script>
    MathJax.Hub.Config({
        "HTML-CSS": {
            matchFontHeight: false
        },
        SVG: {
            matchFontHeight: false
        },
        CommonHTML: {
            matchFontHeight: false
        },
        tex2jax: {
            inlineMath: [
                ['$','$'],
                ['\\(','\\)']
            ]
        }
    });
</script>

    
    
    
    
<script src="//cdnjs.cloudflare.com/ajax/libs/lightgallery/1.6.8/js/lightgallery-all.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/justifiedGallery/3.6.5/js/jquery.justifiedGallery.min.js"></script>
<script>
    (function ($) {
        $(document).ready(function () {
            if (typeof($.fn.lightGallery) === 'function') {
                $('.article.gallery').lightGallery({ selector: '.gallery-item' });
            }
            if (typeof($.fn.justifiedGallery) === 'function') {
                $('.justified-gallery').justifiedGallery();
            }
        });
    })(jQuery);
</script>

    
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js"></script>
    <style>
        .hljs {
            position: relative;
        }

        .hljs .clipboard-btn {
            float: right;
            color: #9a9a9a;
            background: none;
            border: none;
            cursor: pointer;
        }

        .hljs .clipboard-btn:hover {
          color: #8a8a8a;
        }

        .hljs > .clipboard-btn {
            display: none;
            position: absolute;
            right: 4px;
            top: 4px;
        }

        .hljs:hover > .clipboard-btn {
            display: inline;
        }

        .hljs > figcaption > .clipboard-btn {
            margin-right: 4px;
        }
    </style>
    <script>
      $(document).ready(function () {
        $('figure.hljs').each(function(i, figure) {
          var codeId = 'code-' + i;
          var code = figure.querySelector('.code');
          var copyButton = $('<button>Copy <i class="far fa-clipboard"></i></button>');
          code.id = codeId;
          copyButton.addClass('clipboard-btn');
          copyButton.attr('data-clipboard-target-id', codeId);

          var figcaption = figure.querySelector('figcaption');

          if (figcaption) {
            figcaption.append(copyButton[0]);
          } else {
            figure.prepend(copyButton[0]);
          }
        })

        var clipboard = new ClipboardJS('.clipboard-btn', {
          target: function(trigger) {
            return document.getElementById(trigger.getAttribute('data-clipboard-target-id'));
          }
        });
        clipboard.on('success', function(e) {
          e.clearSelection();
        })
      })
    </script>

    
    

    



<script src="/js/script.js"></script>


    
    <div class="searchbox ins-search">
    <div class="searchbox-mask"></div>
    <div class="searchbox-container ins-search-container">
        <div class="searchbox-input-wrapper">
            <input type="text" class="searchbox-input ins-search-input" placeholder="Type something..." />
            <span class="searchbox-close ins-close ins-selectable"><i class="fa fa-times-circle"></i></span>
        </div>
        <div class="searchbox-result-wrapper ins-section-wrapper">
            <div class="ins-section-container"></div>
        </div>
    </div>
</div>
<script>
    (function (window) {
        var INSIGHT_CONFIG = {
            TRANSLATION: {
                POSTS: 'Posts',
                PAGES: 'Pages',
                CATEGORIES: 'Categories',
                TAGS: 'Tags',
                UNTITLED: '(Untitled)',
            },
            CONTENT_URL: '/content.json',
        };
        window.INSIGHT_CONFIG = INSIGHT_CONFIG;
    })(window);
</script>

<script src="/js/insight.js"></script>

    
</body>
</html>